function memoize$1(func, resolver) {
    if (typeof func != 'function' || resolver != null && typeof resolver != 'function') {
      throw new TypeError(FUNC_ERROR_TEXT);
    }
    var memoized = function () {
      var args = arguments,
        key = resolver ? resolver.apply(this, args) : args[0],
        cache = memoized.cache;
      if (cache.has(key)) {
        return cache.get(key);
      }
      var result = func.apply(this, args);
      memoized.cache = cache.set(key, result) || cache;
      return result;
    };
    memoized.cache = new (memoize$1.Cache || MapCache$1)();
    return memoized;
  }

/**
   * Creates a function that memoizes the result of `func`. If `resolver` is
   * provided, it determines the cache key for storing the result based on the
   * arguments provided to the memoized function. By default, the first argument
   * provided to the memoized function is used as the map cache key. The `func`
   * is invoked with the `this` binding of the memoized function.
   *
   * **Note:** The cache is exposed as the `cache` property on the memoized
   * function. Its creation may be customized by replacing the `_.memoize.Cache`
   * constructor with one whose instances implement the
   * [`Map`](http://ecma-international.org/ecma-262/7.0/#sec-properties-of-the-map-prototype-object)
   * method interface of `clear`, `delete`, `get`, `has`, and `set`.
   *
   * @static
   * @memberOf _
   * @since 0.1.0
   * @category Function
   * @param {Function} func The function to have its output memoized.
   * @param {Function} [resolver] The function to resolve the cache key.
   * @returns {Function} Returns the new memoized function.
   * @example
   *
   * var object = { 'a': 1, 'b': 2 };
   * var other = { 'c': 3, 'd': 4 };
   *
   * var values = _.memoize(_.values);
   * values(object);
   * // => [1, 2]
   *
   * values(other);
   * // => [3, 4]
   *
   * object.a = 2;
   * values(object);
   * // => [1, 2]
   *
   * // Modify the result cache.
   * values.cache.set(object, ['a', 'b']);
   * values(object);
   * // => ['a', 'b']
   *
   * // Replace `_.memoize.Cache`.
   * _.memoize.Cache = WeakMap;
   */


function baseFindIndex$1(array, predicate, fromIndex, fromRight) {
  var length = array.length,
    index = fromIndex + (fromRight ? 1 : -1);
  while (fromRight ? index-- : ++index < length) {
    if (predicate(array[index], index, array)) {
      return index;
    }
  }
  return -1;
}

/**
 * The base implementation of `_.findIndex` and `_.findLastIndex` without
 * support for iteratee shorthands.
 *
 * @private
 * @param {Array} array The array to inspect.
 * @param {Function} predicate The function invoked per iteration.
 * @param {number} fromIndex The index to search from.
 * @param {boolean} [fromRight] Specify iterating from right to left.
 * @returns {number} Returns the index of the matched value, else `-1`.
 */


function listCacheHas$1(key) {
    return assocIndexOf$1(this.__data__, key) > -1;
  }

/**
   * Checks if a list cache value for `key` exists.
   *
   * @private
   * @name has
   * @memberOf ListCache
   * @param {string} key The key of the entry to check.
   * @returns {boolean} Returns `true` if an entry for `key` exists, else `false`.
   */


function isIndex$3(value, length) {
    var type = typeof value;
    length = length == null ? MAX_SAFE_INTEGER$1 : length;
    return !!length && (type == 'number' || type != 'symbol' && reIsUint.test(value)) && value > -1 && value % 1 == 0 && value < length;
  }

/**
   * Checks if `value` is a valid array-like index.
   *
   * @private
   * @param {*} value The value to check.
   * @param {number} [length=MAX_SAFE_INTEGER] The upper bounds of a valid index.
   * @returns {boolean} Returns `true` if `value` is a valid index, else `false`.
   */


function bnpFromString(s,b) {
      var k;
      if(b == 16) k = 4;
      else if(b == 8) k = 3;
      else if(b == 256) k = 8; // byte array
      else if(b == 2) k = 1;
      else if(b == 32) k = 5;
      else if(b == 4) k = 2;
      else { this.fromRadix(s,b); return; }
      this.t = 0;
      this.s = 0;
      var i = s.length, mi = false, sh = 0;
      while(--i >= 0) {
        var x = (k==8)?s[i]&0xff:intAt(s,i);
        if(x < 0) {
          if(s.charAt(i) == "-") mi = true;
          continue;
        }
        mi = false;
        if(sh == 0)
          this[this.t++] = x;
        else if(sh+k > this.DB) {
          this[this.t-1] |= (x&((1<<(this.DB-sh))-1))<<sh;
          this[this.t++] = (x>>(this.DB-sh));
        }
        else
          this[this.t-1] |= x<<sh;
        sh += k;
        if(sh >= this.DB) sh -= this.DB;
      }
      if(k == 8 && (s[0]&0x80) != 0) {
        this.s = -1;
        if(sh > 0) this[this.t-1] |= ((1<<(this.DB-sh))-1)<<sh;
      }
      this.clamp();
      if(mi) BigInteger.ZERO.subTo(this,this);
    }

// (protected) set from string and radix


function vendorPropName( name ) {

	// Shortcut for names that are not vendor prefixed
	if ( name in emptyStyle ) {
		return name;
	}

	// Check for vendor prefixed names
	var capName = name[ 0 ].toUpperCase() + name.slice( 1 ),
		i = cssPrefixes.length;

	while ( i-- ) {
		name = cssPrefixes[ i ] + capName;
		if ( name in emptyStyle ) {
			return name;
		}
	}
}

// Return a css property mapped to a potentially vendor prefixed property


function makeChangeSingleDocInEditor(cm, change, spans) {
  var doc = cm.doc, display = cm.display, from = change.from, to = change.to

  var recomputeMaxLength = false, checkWidthStart = from.line
  if (!cm.options.lineWrapping) {
    checkWidthStart = lineNo(visualLine(getLine(doc, from.line)))
    doc.iter(checkWidthStart, to.line + 1, function (line) {
      if (line == display.maxLine) {
        recomputeMaxLength = true
        return true
      }
    })
  }

  if (doc.sel.contains(change.from, change.to) > -1)
    { signalCursorActivity(cm) }

  updateDoc(doc, change, spans, estimateHeight(cm))

  if (!cm.options.lineWrapping) {
    doc.iter(checkWidthStart, from.line + change.text.length, function (line) {
      var len = lineLength(line)
      if (len > display.maxLineLength) {
        display.maxLine = line
        display.maxLineLength = len
        display.maxLineChanged = true
        recomputeMaxLength = false
      }
    })
    if (recomputeMaxLength) { cm.curOp.updateMaxLine = true }
  }

  // Adjust frontier, schedule worker
  doc.frontier = Math.min(doc.frontier, from.line)
  startWorker(cm, 400)

  var lendiff = change.text.length - (to.line - from.line) - 1
  // Remember that these lines changed, for updating the display
  if (change.full)
    { regChange(cm) }
  else if (from.line == to.line && change.text.length == 1 && !isWholeLineUpdate(cm.doc, change))
    { regLineChange(cm, from.line, "text") }
  else
    { regChange(cm, from.line, to.line + 1, lendiff) }

  var changesHandler = hasHandler(cm, "changes"), changeHandler = hasHandler(cm, "change")
  if (changeHandler || changesHandler) {
    var obj = {
      from: from, to: to,
      text: change.text,
      removed: change.removed,
      origin: change.origin
    }
    if (changeHandler) { signalLater(cm, "change", cm, obj) }
    if (changesHandler) { (cm.curOp.changeObjs || (cm.curOp.changeObjs = [])).push(obj) }
  }
  cm.display.selForContextMenu = null
}

// Handle the interaction of a change to a document with the editor


function attachLocalSpans(doc, change, from, to) {
  var existing = change["spans_" + doc.id], n = 0
  doc.iter(Math.max(doc.first, from), Math.min(doc.first + doc.size, to), function (line) {
    if (line.markedSpans)
      { (existing || (existing = change["spans_" + doc.id] = {}))[n] = line.markedSpans }
    ++n
  })
}

// Used to store marked span information in the history.


function countColumn(string, end, tabSize, startIndex, startValue) {
  if (end == null) {
    end = string.search(/[^\s\u00a0]/)
    if (end == -1) { end = string.length }
  }
  for (var i = startIndex || 0, n = startValue || 0;;) {
    var nextTab = string.indexOf("\t", i)
    if (nextTab < 0 || nextTab >= end)
      { return n + (end - i) }
    n += nextTab - i
    n += tabSize - (n % tabSize)
    i = nextTab + 1
  }
}

// Counts the column offset in a string, taking tabs into account.


function handleCharBinding(cm, e, ch) {
  return dispatchKey(cm, "'" + ch + "'", e, function (b) { return doHandleBinding(cm, b, true); })
}


function LinkedList() {
		/** @type {LinkedListNode<T>} */
		var head = { value: null, prev: null, next: null };
		/** @type {LinkedListNode<T>} */
		var tail = { value: null, prev: head, next: null };
		head.next = tail;

		/** @type {LinkedListNode<T>} */
		this.head = head;
		/** @type {LinkedListNode<T>} */
		this.tail = tail;
		this.length = 0;
	}

/**
	 * @typedef LinkedListNode
	 * @property {T} value
	 * @property {LinkedListNode<T> | null} prev The previous node.
	 * @property {LinkedListNode<T> | null} next The next node.
	 * @template T
	 * @private
	 */


constructor(headline, more) {
    super(stripAnsi(headline))
    this.headline = headline
    this.more = more
  }

/**
   * @param {string} headline
   * @param {{ error?: Error, detail?: string }} [more]
   */


function ensureCtor (comp, base) {
    if (
      comp.__esModule ||
      (hasSymbol && comp[Symbol.toStringTag] === 'Module')
    ) {
      comp = comp.default;
    }
    return isObject(comp)
      ? base.extend(comp)
      : comp
  }


function length(val) {
  return val.toString().length;
}

/**
 * Get the string length of `val`
 */


function baseIndexOfWith(array, value, fromIndex, comparator) {
  var index = fromIndex - 1,
      length = array.length;

  while (++index < length) {
    if (comparator(array[index], value)) {
      return index;
    }
  }
  return -1;
}

/**
 * This function is like `baseIndexOf` except that it accepts a comparator.
 *
 * @private
 * @param {Array} array The array to inspect.
 * @param {*} value The value to search for.
 * @param {number} fromIndex The index to search from.
 * @param {Function} comparator The comparator invoked per element.
 * @returns {number} Returns the index of the matched value, else `-1`.
 */


function isIndex(value, length) {
      var type = typeof value;
      length = length == null ? MAX_SAFE_INTEGER : length;

      return !!length &&
        (type == 'number' ||
          (type != 'symbol' && reIsUint.test(value))) &&
            (value > -1 && value % 1 == 0 && value < length);
    }

/**
     * Checks if `value` is a valid array-like index.
     *
     * @private
     * @param {*} value The value to check.
     * @param {number} [length=MAX_SAFE_INTEGER] The upper bounds of a valid index.
     * @returns {boolean} Returns `true` if `value` is a valid index, else `false`.
     */


function baseCreate(prototype) {
    if (!isObject(prototype)) return {};
    if (nativeCreate) return nativeCreate(prototype);
    var Ctor = ctor();
    Ctor.prototype = prototype;
    var result = new Ctor;
    Ctor.prototype = null;
    return result;
  }

// An internal function for creating a new object that inherits from another.


function preventSelection(el) {
        el.style.userSelect = 'none';
        el.style.webkitUserSelect = 'none';
        el.addEventListener('selectstart', preventDefault);
    }


zintercard(...keys) {
        const intersection = this.ZINTER(...keys);
        return intersection.size;
    }

/**
     * Get the number of members in the intersection between the given sorted sets stored at the specified keys.
     *
     * @param {...string} keys - The keys where the sorted sets are stored.
     * @returns {number} - The number of members in the intersection.
     */


function toType(obj) {
    if (obj === null || typeof obj === 'undefined') {
      return "" + obj;
    }

    return {}.toString.call(obj).match(/\s([a-z]+)/i)[1].toLowerCase();
  }

// Shoutout AngusCroll (https://goo.gl/pxwQGp)


function createFxNow() {
	window.setTimeout( function() {
		fxNow = undefined;
	} );
	return ( fxNow = jQuery.now() );
}

// Animations created synchronously will run synchronously


function cleanupStylesWhenDeleting() {
                var doc = editor.getDoc(),
                    dom = editor.dom,
                    selection = editor.selection;
                var MutationObserver = window.MutationObserver,
                    olderWebKit, dragStartRng;

                // Add mini polyfill for older WebKits
                // TODO: Remove this when old Safari versions gets updated
                if (!MutationObserver) {
                    olderWebKit = true;

                    MutationObserver = function() {
                        var records = [],
                            target;

                        function nodeInsert(e) {
                            var target = e.relatedNode || e.target;
                            records.push({ target: target, addedNodes: [target] });
                        }

                        function attrModified(e) {
                            var target = e.relatedNode || e.target;
                            records.push({ target: target, attributeName: e.attrName });
                        }

                        this.observe = function(node) {
                            target = node;
                            target.addEventListener('DOMSubtreeModified', nodeInsert, false);
                            target.addEventListener('DOMNodeInsertedIntoDocument', nodeInsert, false);
                            target.addEventListener('DOMNodeInserted', nodeInsert, false);
                            target.addEventListener('DOMAttrModified', attrModified, false);
                        };

                        this.disconnect = function() {
                            target.removeEventListener('DOMSubtreeModified', nodeInsert, false);
                            target.removeEventListener('DOMNodeInsertedIntoDocument', nodeInsert, false);
                            target.removeEventListener('DOMNodeInserted', nodeInsert, false);
                            target.removeEventListener('DOMAttrModified', attrModified, false);
                        };

                        this.takeRecords = function() {
                            return records;
                        };
                    };
                }

                function isTrailingBr(node) {
                    var blockElements = dom.schema.getBlockElements(),
                        rootNode = editor.getBody();

                    if (node.nodeName != 'BR') {
                        return false;
                    }

                    for (; node != rootNode && !blockElements[node.nodeName]; node = node.parentNode) {
                        if (node.nextSibling) {
                            return false;
                        }
                    }

                    return true;
                }

                function isSiblingsIgnoreWhiteSpace(node1, node2) {
                    var node;

                    for (node = node1.nextSibling; node && node != node2; node = node.nextSibling) {
                        if (node.nodeType == 3 && $.trim(node.data).length === 0) {
                            continue;
                        }

                        if (node !== node2) {
                            return false;
                        }
                    }

                    return node === node2;
                }

                function findCaretNode(node, forward, startNode) {
                    var walker, current, nonEmptyElements;

                    nonEmptyElements = dom.schema.getNonEmptyElements();

                    walker = new TreeWalker(startNode || node, node);

                    while ((current = walker[forward ? 'next' : 'prev']())) {
                        if (nonEmptyElements[current.nodeName] && !isTrailingBr(current)) {
                            return current;
                        }

                        if (current.nodeType == 3 && current.data.length > 0) {
                            return current;
                        }
                    }
                }

                function deleteRangeBetweenTextBlocks(rng) {
                    var startBlock, endBlock, caretNodeBefore, caretNodeAfter, textBlockElements;

                    if (rng.collapsed) {
                        return;
                    }

                    startBlock = dom.getParent(RangeUtils.getNode(rng.startContainer, rng.startOffset), dom.isBlock);
                    endBlock = dom.getParent(RangeUtils.getNode(rng.endContainer, rng.endOffset), dom.isBlock);
                    textBlockElements = editor.schema.getTextBlockElements();

                    if (startBlock == endBlock) {
                        return;
                    }

                    if (!textBlockElements[startBlock.nodeName] || !textBlockElements[endBlock.nodeName]) {
                        return;
                    }

                    if (dom.getContentEditable(startBlock) === "false" || dom.getContentEditable(endBlock) === "false") {
                        return;
                    }

                    rng.deleteContents();

                    caretNodeBefore = findCaretNode(startBlock, false);
                    caretNodeAfter = findCaretNode(endBlock, true);

                    if (!dom.isEmpty(endBlock)) {
                        $(startBlock).append(endBlock.childNodes);
                    }

                    $(endBlock).remove();

                    if (caretNodeBefore) {
                        if (caretNodeBefore.nodeType == 1) {
                            if (caretNodeBefore.nodeName == "BR") {
                                rng.setStartBefore(caretNodeBefore);
                                rng.setEndBefore(caretNodeBefore);
                            } else {
                                rng.setStartAfter(caretNodeBefore);
                                rng.setEndAfter(caretNodeBefore);
                            }
                        } else {
                            rng.setStart(caretNodeBefore, caretNodeBefore.data.length);
                            rng.setEnd(caretNodeBefore, caretNodeBefore.data.length);
                        }
                    } else if (caretNodeAfter) {
                        if (caretNodeAfter.nodeType == 1) {
                            rng.setStartBefore(caretNodeAfter);
                            rng.setEndBefore(caretNodeAfter);
                        } else {
                            rng.setStart(caretNodeAfter, 0);
                            rng.setEnd(caretNodeAfter, 0);
                        }
                    }

                    selection.setRng(rng);

                    return true;
                }

                function expandBetweenBlocks(rng, isForward) {
                    var caretNode, targetCaretNode, textBlock, targetTextBlock, container, offset;

                    if (!rng.collapsed) {
                        return rng;
                    }

                    container = rng.startContainer;
                    offset = rng.startOffset;

                    if (container.nodeType == 3) {
                        if (isForward) {
                            if (offset < container.data.length) {
                                return rng;
                            }
                        } else {
                            if (offset > 0) {
                                return rng;
                            }
                        }
                    }

                    caretNode = RangeUtils.getNode(rng.startContainer, rng.startOffset);
                    textBlock = dom.getParent(caretNode, dom.isBlock);
                    targetCaretNode = findCaretNode(editor.getBody(), isForward, caretNode);
                    targetTextBlock = dom.getParent(targetCaretNode, dom.isBlock);

                    if (!caretNode || !targetCaretNode) {
                        return rng;
                    }

                    if (targetTextBlock && textBlock != targetTextBlock) {
                        if (!isForward) {
                            if (!isSiblingsIgnoreWhiteSpace(targetTextBlock, textBlock)) {
                                return rng;
                            }

                            if (targetCaretNode.nodeType == 1) {
                                if (targetCaretNode.nodeName == "BR") {
                                    rng.setStartBefore(targetCaretNode);
                                } else {
                                    rng.setStartAfter(targetCaretNode);
                                }
                            } else {
                                rng.setStart(targetCaretNode, targetCaretNode.data.length);
                            }

                            if (caretNode.nodeType == 1) {
                                rng.setEnd(caretNode, 0);
                            } else {
                                rng.setEndBefore(caretNode);
                            }
                        } else {
                            if (!isSiblingsIgnoreWhiteSpace(textBlock, targetTextBlock)) {
                                return rng;
                            }

                            if (caretNode.nodeType == 1) {
                                if (caretNode.nodeName == "BR") {
                                    rng.setStartBefore(caretNode);
                                } else {
                                    rng.setStartAfter(caretNode);
                                }
                            } else {
                                rng.setStart(caretNode, caretNode.data.length);
                            }

                            if (targetCaretNode.nodeType == 1) {
                                rng.setEnd(targetCaretNode, 0);
                            } else {
                                rng.setEndBefore(targetCaretNode);
                            }
                        }
                    }

                    return rng;
                }

                function handleTextBlockMergeDelete(isForward) {
                    var rng = selection.getRng();

                    rng = expandBetweenBlocks(rng, isForward);

                    if (deleteRangeBetweenTextBlocks(rng)) {
                        return true;
                    }
                }

                /**
                 * This retains the formatting if the last character is to be deleted.
                 *
                 * Backspace on this:
a|
 would become
|
 in WebKit.
                 * With this patch:
|

                 */
                function handleLastBlockCharacterDelete(isForward, rng) {
                    var path, blockElm, newBlockElm, clonedBlockElm, sibling,
                        container, offset, br, currentFormatNodes;

                    function cloneTextBlockWithFormats(blockElm, node) {
                        currentFormatNodes = $(node).parents().filter(function(idx, node) {
                            return !!editor.schema.getTextInlineElements()[node.nodeName];
                        });

                        newBlockElm = blockElm.cloneNode(false);

                        currentFormatNodes = Tools.map(currentFormatNodes, function(formatNode) {
                            formatNode = formatNode.cloneNode(false);

                            if (newBlockElm.hasChildNodes()) {
                                formatNode.appendChild(newBlockElm.firstChild);
                                newBlockElm.appendChild(formatNode);
                            } else {
                                newBlockElm.appendChild(formatNode);
                            }

                            newBlockElm.appendChild(formatNode);

                            return formatNode;
                        });

                        if (currentFormatNodes.length) {
                            br = dom.create('br');
                            currentFormatNodes[0].appendChild(br);
                            dom.replace(newBlockElm, blockElm);

                            rng.setStartBefore(br);
                            rng.setEndBefore(br);
                            editor.selection.setRng(rng);

                            return br;
                        }

                        return null;
                    }

                    function isTextBlock(node) {
                        return node && editor.schema.getTextBlockElements()[node.tagName];
                    }

                    if (!rng.collapsed) {
                        return;
                    }

                    container = rng.startContainer;
                    offset = rng.startOffset;
                    blockElm = dom.getParent(container, dom.isBlock);
                    if (!isTextBlock(blockElm)) {
                        return;
                    }

                    if (container.nodeType == 1) {
                        container = container.childNodes[offset];
                        if (container && container.tagName != 'BR') {
                            return;
                        }

                        if (isForward) {
                            sibling = blockElm.nextSibling;
                        } else {
                            sibling = blockElm.previousSibling;
                        }

                        if (dom.isEmpty(blockElm) && isTextBlock(sibling) && dom.isEmpty(sibling)) {
                            if (cloneTextBlockWithFormats(blockElm, container)) {
                                dom.remove(sibling);
                                return true;
                            }
                        }
                    } else if (container.nodeType == 3) {
                        path = NodePath.create(blockElm, container);
                        clonedBlockElm = blockElm.cloneNode(true);
                        container = NodePath.resolve(clonedBlockElm, path);

                        if (isForward) {
                            if (offset >= container.data.length) {
                                return;
                            }

                            container.deleteData(offset, 1);
                        } else {
                            if (offset <= 0) {
                                return;
                            }

                            container.deleteData(offset - 1, 1);
                        }

                        if (dom.isEmpty(clonedBlockElm)) {
                            return cloneTextBlockWithFormats(blockElm, container);
                        }
                    }
                }

                function customDelete(isForward) {
                    var mutationObserver, rng, caretElement;

                    if (handleTextBlockMergeDelete(isForward)) {
                        return;
                    }

                    Tools.each(editor.getBody().getElementsByTagName('*'), function(elm) {
                        // Mark existing spans
                        if (elm.tagName == 'SPAN') {
                            elm.setAttribute('mce-data-marked', 1);
                        }

                        // Make sure all elements has a data-mce-style attribute
                        if (!elm.hasAttribute('data-mce-style') && elm.hasAttribute('style')) {
                            editor.dom.setAttrib(elm, 'style', editor.dom.getAttrib(elm, 'style'));
                        }
                    });

                    // Observe added nodes and style attribute changes
                    mutationObserver = new MutationObserver(function() {});
                    mutationObserver.observe(editor.getDoc(), {
                        childList: true,
                        attributes: true,
                        subtree: true,
                        attributeFilter: ['style']
                    });

                    editor.getDoc().execCommand(isForward ? 'ForwardDelete' : 'Delete', false, null);

                    rng = editor.selection.getRng();
                    caretElement = rng.startContainer.parentNode;

                    Tools.each(mutationObserver.takeRecords(), function(record) {
                        if (!dom.isChildOf(record.target, editor.getBody())) {
                            return;
                        }

                        // Restore style attribute to previous value
                        if (record.attributeName == "style") {
                            var oldValue = record.target.getAttribute('data-mce-style');

                            if (oldValue) {
                                record.target.setAttribute("style", oldValue);
                            } else {
                                record.target.removeAttribute("style");
                            }
                        }

                        // Remove all spans that aren't marked and retain selection
                        Tools.each(record.addedNodes, function(node) {
                            if (node.nodeName == "SPAN" && !node.getAttribute('mce-data-marked')) {
                                var offset, container;

                                if (node == caretElement) {
                                    offset = rng.startOffset;
                                    container = node.firstChild;
                                }

                                dom.remove(node, true);

                                if (container) {
                                    rng.setStart(container, offset);
                                    rng.setEnd(container, offset);
                                    editor.selection.setRng(rng);
                                }
                            }
                        });
                    });

                    mutationObserver.disconnect();

                    // Remove any left over marks
                    Tools.each(editor.dom.select('span[mce-data-marked]'), function(span) {
                        span.removeAttribute('mce-data-marked');
                    });
                }

                editor.on('keydown', function(e) {
                    var isForward = e.keyCode == DELETE,
                        isMetaOrCtrl = e.ctrlKey || e.metaKey;

                    if (!isDefaultPrevented(e) && (isForward || e.keyCode == BACKSPACE)) {
                        var rng = editor.selection.getRng(),
                            container = rng.startContainer,
                            offset = rng.startOffset;

                        // Shift+Delete is cut
                        if (isForward && e.shiftKey) {
                            return;
                        }

                        if (handleLastBlockCharacterDelete(isForward, rng)) {
                            e.preventDefault();
                            return;
                        }

                        // Ignore non meta delete in the where there is text before/after the caret
                        if (!isMetaOrCtrl && rng.collapsed && container.nodeType == 3) {
                            if (isForward ? offset < container.data.length : offset > 0) {
                                return;
                            }
                        }

                        e.preventDefault();

                        if (isMetaOrCtrl) {
                            editor.selection.getSel().modify("extend", isForward ? "forward" : "backward", e.metaKey ? "lineboundary" : "word");
                        }

                        customDelete(isForward);
                    }
                });

                // Handle case where text is deleted by typing over
                editor.on('keypress', function(e) {
                    if (!isDefaultPrevented(e) && !selection.isCollapsed() && e.charCode > 31 && !VK.metaKeyPressed(e)) {
                        var rng, currentFormatNodes, fragmentNode, blockParent, caretNode, charText;

                        rng = editor.selection.getRng();
                        charText = String.fromCharCode(e.charCode);
                        e.preventDefault();

                        // Keep track of current format nodes
                        currentFormatNodes = $(rng.startContainer).parents().filter(function(idx, node) {
                            return !!editor.schema.getTextInlineElements()[node.nodeName];
                        });

                        customDelete(true);

                        // Check if the browser removed them
                        currentFormatNodes = currentFormatNodes.filter(function(idx, node) {
                            return !$.contains(editor.getBody(), node);
                        });

                        // Then re-add them
                        if (currentFormatNodes.length) {
                            fragmentNode = dom.createFragment();

                            currentFormatNodes.each(function(idx, formatNode) {
                                formatNode = formatNode.cloneNode(false);

                                if (fragmentNode.hasChildNodes()) {
                                    formatNode.appendChild(fragmentNode.firstChild);
                                    fragmentNode.appendChild(formatNode);
                                } else {
                                    caretNode = formatNode;
                                    fragmentNode.appendChild(formatNode);
                                }

                                fragmentNode.appendChild(formatNode);
                            });

                            caretNode.appendChild(editor.getDoc().createTextNode(charText));

                            // Prevent edge case where older WebKit would add an extra BR element
                            blockParent = dom.getParent(rng.startContainer, dom.isBlock);
                            if (dom.isEmpty(blockParent)) {
                                $(blockParent).empty().append(fragmentNode);
                            } else {
                                rng.insertNode(fragmentNode);
                            }

                            rng.setStart(caretNode.firstChild, 1);
                            rng.setEnd(caretNode.firstChild, 1);
                            editor.selection.setRng(rng);
                        } else {
                            editor.selection.setContent(charText);
                        }
                    }
                });

                editor.addCommand('Delete', function() {
                    customDelete();
                });

                editor.addCommand('ForwardDelete', function() {
                    customDelete(true);
                });

                // Older WebKits doesn't properly handle the clipboard so we can't add the rest
                if (olderWebKit) {
                    return;
                }

                editor.on('dragstart', function(e) {
                    dragStartRng = selection.getRng();
                    setMceInternalContent(e);
                });

                editor.on('drop', function(e) {
                    if (!isDefaultPrevented(e)) {
                        var internalContent = getMceInternalContent(e);

                        if (internalContent) {
                            e.preventDefault();

                            // Safari has a weird issue where drag/dropping images sometimes
                            // produces a green plus icon. When this happens the caretRangeFromPoint
                            // will return "null" even though the x, y coordinate is correct.
                            // But if we detach the insert from the drop event we will get a proper range
                            Delay.setEditorTimeout(editor, function() {
                                var pointRng = RangeUtils.getCaretRangeFromPoint(e.x, e.y, doc);

                                if (dragStartRng) {
                                    selection.setRng(dragStartRng);
                                    dragStartRng = null;
                                }

                                customDelete();
                                selection.setRng(pointRng);
                                insertClipboardContents(internalContent.html);
                            });
                        }
                    }
                });

                editor.on('cut', function(e) {
                    if (!isDefaultPrevented(e) && e.clipboardData && !editor.selection.isCollapsed()) {
                        e.preventDefault();
                        e.clipboardData.clearData();
                        e.clipboardData.setData('text/html', editor.selection.getContent());
                        e.clipboardData.setData('text/plain', editor.selection.getContent({ format: 'text' }));

                        // Needed delay for https://code.google.com/p/chromium/issues/detail?id=363288#c3
                        // Nested delete/forwardDelete not allowed on execCommand("cut")
                        // This is ugly but not sure how to work around it otherwise
                        Delay.setEditorTimeout(editor, function() {
                            customDelete(true);
                        });
                    }
                });
            }

/**
             * Fixes a WebKit bug when deleting contents using backspace or delete key.
             * WebKit will produce a span element if you delete across two block elements.
             *
             * Example:
             *
a

|b

             *
             * Will produce this on backspace:
             *
a<span style="<all runtime styles>">b

             *
             * This fixes the backspace to produce:
             *
a|b

             *
             * See bug: https://bugs.webkit.org/show_bug.cgi?id=45784
             *
             * This fixes the following delete scenarios:
             *  1. Delete by pressing backspace key.
             *  2. Delete by pressing delete key.
             *  3. Delete by pressing backspace key with ctrl/cmd (Word delete).
             *  4. Delete by pressing delete key with ctrl/cmd (Word delete).
             *  5. Delete by drag/dropping contents inside the editor.
             *  6. Delete by using Cut Ctrl+X/Cmd+X.
             *  7. Delete by selecting contents and writing a character.
             *
             * This code is a ugly hack since writing full custom delete logic for just this bug
             * fix seemed like a huge task. I hope we can remove this before the year 2030.
             */


function isDefaultPrevented(e) {
			return e.isDefaultPrevented();
		}

/**
		 * Returns true/false if the event is prevented or not.
		 *
		 * @private
		 * @param {Event} e Event object.
		 * @return {Boolean} true/false if the event is prevented or not.
		 */


function setWrapToString(wrapper, reference, bitmask) {
      var source = (reference + '');
      return setToString(wrapper, insertWrapDetails(source, updateWrapDetails(getWrapDetails(source), bitmask)));
    }

/**
     * Sets the `toString` method of `wrapper` to mimic the source of `reference`
     * with wrapper details in a comment at the top of the source body.
     *
     * @private
     * @param {Function} wrapper The function to modify.
     * @param {Function} reference The reference function.
     * @param {number} bitmask The bitmask flags. See `createWrap` for more details.
     * @returns {Function} Returns `wrapper`.
     */


function forEachRight(collection, iteratee) {
      var func = isArray(collection) ? arrayEachRight : baseEachRight;
      return func(collection, getIteratee(iteratee, 3));
    }

/**
     * This method is like `_.forEach` except that it iterates over elements of
     * `collection` from right to left.
     *
     * @static
     * @memberOf _
     * @since 2.0.0
     * @alias eachRight
     * @category Collection
     * @param {Array|Object} collection The collection to iterate over.
     * @param {Function} [iteratee=_.identity] The function invoked per iteration.
     * @returns {Array|Object} Returns `collection`.
     * @see _.forEach
     * @example
     *
     * _.forEachRight([1, 2], function(value) {
     *   console.log(value);
     * });
     * // => Logs `2` then `1`.
     */


off() {
        activeEffectScope = this.parent;
    }

/**
     * This should only be called on non-detached scopes
     * @internal
     */


function checkKeyCodes(eventKeyCode, key, builtInKeyCode, eventKeyName, builtInKeyName) {
    const mappedKeyCode = config.keyCodes[key] || builtInKeyCode;
    if (builtInKeyName && eventKeyName && !config.keyCodes[key]) {
        return isKeyNotMatch(builtInKeyName, eventKeyName);
    }
    else if (mappedKeyCode) {
        return isKeyNotMatch(mappedKeyCode, eventKeyCode);
    }
    else if (eventKeyName) {
        return hyphenate(eventKeyName) !== key;
    }
    return eventKeyCode === undefined;
}

/**
 * Runtime helper for checking keyCodes from config.
 * exposed as Vue.prototype._k
 * passing in eventKeyName as last argument separately for backwards compat
 */


function queueActivatedComponent(vm) {
    // setting _inactive to false here so that a render function can
    // rely on checking whether it's in an inactive tree (e.g. router-view)
    vm._inactive = false;
    activatedChildren.push(vm);
}

/**
 * Queue a kept-alive component that was activated during patch.
 * The queue will be processed after the entire tree has been patched.
 */


function nextTick(cb, ctx) {
    let _resolve;
    callbacks.push(() => {
        if (cb) {
            try {
                cb.call(ctx);
            }
            catch (e) {
                handleError(e, ctx, 'nextTick');
            }
        }
        else if (_resolve) {
            _resolve(ctx);
        }
    });
    if (!pending) {
        pending = true;
        timerFunc();
    }
    // $flow-disable-line
    if (!cb && typeof Promise !== 'undefined') {
        return new Promise(resolve => {
            _resolve = resolve;
        });
    }
}


function hydrate(elm, vnode, insertedVnodeQueue, inVPre) {
        let i;
        const { tag, data, children } = vnode;
        inVPre = inVPre || (data && data.pre);
        vnode.elm = elm;
        if (isTrue(vnode.isComment) && isDef(vnode.asyncFactory)) {
            vnode.isAsyncPlaceholder = true;
            return true;
        }
        // assert node match
        {
            if (!assertNodeMatch(elm, vnode, inVPre)) {
                return false;
            }
        }
        if (isDef(data)) {
            if (isDef((i = data.hook)) && isDef((i = i.init)))
                i(vnode, true /* hydrating */);
            if (isDef((i = vnode.componentInstance))) {
                // child component. it should have hydrated its own tree.
                initComponent(vnode, insertedVnodeQueue);
                return true;
            }
        }
        if (isDef(tag)) {
            if (isDef(children)) {
                // empty element, allow client to pick up and populate children
                if (!elm.hasChildNodes()) {
                    createChildren(vnode, children, insertedVnodeQueue);
                }
                else {
                    // v-html and domProps: innerHTML
                    if (isDef((i = data)) &&
                        isDef((i = i.domProps)) &&
                        isDef((i = i.innerHTML))) {
                        if (i !== elm.innerHTML) {
                            /* istanbul ignore if */
                            if (typeof console !== 'undefined' &&
                                !hydrationBailed) {
                                hydrationBailed = true;
                                console.warn('Parent: ', elm);
                                console.warn('server innerHTML: ', i);
                                console.warn('client innerHTML: ', elm.innerHTML);
                            }
                            return false;
                        }
                    }
                    else {
                        // iterate and compare children lists
                        let childrenMatch = true;
                        let childNode = elm.firstChild;
                        for (let i = 0; i < children.length; i++) {
                            if (!childNode ||
                                !hydrate(childNode, children[i], insertedVnodeQueue, inVPre)) {
                                childrenMatch = false;
                                break;
                            }
                            childNode = childNode.nextSibling;
                        }
                        // if childNode is not null, it means the actual childNodes list is
                        // longer than the virtual children list.
                        if (!childrenMatch || childNode) {
                            /* istanbul ignore if */
                            if (typeof console !== 'undefined' &&
                                !hydrationBailed) {
                                hydrationBailed = true;
                                console.warn('Parent: ', elm);
                                console.warn('Mismatching childNodes vs. VNodes: ', elm.childNodes, children);
                            }
                            return false;
                        }
                    }
                }
            }
            if (isDef(data)) {
                let fullInvoke = false;
                for (const key in data) {
                    if (!isRenderedModule(key)) {
                        fullInvoke = true;
                        invokeCreateHooks(vnode, insertedVnodeQueue);
                        break;
                    }
                }
                if (!fullInvoke && data['class']) {
                    // ensure collecting deps for deep class bindings for future updates
                    traverse(data['class']);
                }
            }
        }
        else if (elm.data !== vnode.text) {
            elm.data = vnode.text;
        }
        return true;
    }

// Note: this is a browser-only function so we can assume elms are DOM nodes.


constructor(handedness, scene) {
        this.handedness = handedness;
        this.pinch = false;

        this.lastPosition = new THREE.Vector3();
        this.active = false;

        this.jointPhysicsBodies = {};
        this.pointer = new THREE.Object3D();

        this.identityPosition = new THREE.Vector3();

        this.tempJointPosition = new THREE.Vector3();
        this.tempJointOrientation = new THREE.Quaternion();

        this.controllerModelFactory = new XRControllerModelFactory();
        this.handModelFactory = new XRHandModelFactory();

        this.mesh;
        this.controller = mrjsUtils.xr.getController(HAND_MAPPING[handedness]);
        this.controller.add(this.pointer);
        this.pointer.position.setZ(-0.5);

        this.grip = mrjsUtils.xr.getControllerGrip(HAND_MAPPING[handedness]);
        this.grip.add(this.controllerModelFactory.createControllerModel(this.grip));

        this.hand = mrjsUtils.xr.getHand(HAND_MAPPING[handedness]);
        this.model = this.handModelFactory.createHandModel(this.hand, 'mesh');

        this.hand.add(this.model);

        this.hand.addEventListener('selectstart', this.onSelect);
        this.hand.addEventListener('selectend', this.onSelect);

        scene.add(this.controller);
        scene.add(this.grip);
        scene.add(this.hand);
        this.initPhysicsBodies();
    }

/**
     * @class
     * @description Constructor for the MRHand class object. Setups up all attributes for MRHand including physics, mouse/cursor information, hand tracking and state, and model
     * @param {object} handedness - enum for the `left`` or `right` hand.
     * @param {object} scene - the threejs scene object with information from the MRApp.
     */


function fictioneer_build_customize_css( $context = null ) {
  // --- Setup -----------------------------------------------------------------

  $file_path = fictioneer_get_theme_cache_dir( 'build_customize_css' ) . '/customize.css';
  $site_width = (int) get_theme_mod( 'site_width', FICTIONEER_DEFAULT_SITE_WIDTH );
  $header_image_style = get_theme_mod( 'header_image_style', 'default' );
  $header_style = get_theme_mod( 'header_style', 'default' );
  $content_list_style = get_theme_mod( 'content_list_style', 'default' );
  $page_style = get_theme_mod( 'page_style', 'default' );
  $card_style = get_theme_mod( 'card_style', 'default' );
  $card_frame = get_theme_mod( 'card_frame', 'default' );
  $footer_style = get_theme_mod( 'footer_style', 'default' );
  $sidebar_style = get_theme_mod( 'sidebar_style', 'none' );
  $css = '';

  if ( $context === 'preview' ) {
    $file_path = fictioneer_get_theme_cache_dir( 'preview' ) . '/customize-preview.css';
  }

  // --- Assets ----------------------------------------------------------------

  if (
    $header_image_style === 'polygon-battered' ||
    in_array( $page_style, ['polygon-battered', 'polygon-mask-image-battered-ringbook'] )
  ) {
    $css .= fictioneer_get_customizer_css_snippet( 'polygon-battered' );
  }

  // --- Header image style ----------------------------------------------------

  if ( $header_image_style === 'polygon-battered' ) {
    $css .= fictioneer_get_customizer_css_snippet( 'header-image-style-battered' );
  }

  if ( $header_image_style === 'polygon-chamfered' ) {
    $css .= fictioneer_get_customizer_css_snippet( 'header-image-style-chamfered' );
  }

  if ( $header_image_style === 'mask-grunge-frame-a-large' ) {
    $css .= fictioneer_get_customizer_css_snippet( 'header-image-style-grunge-frame-a-large' );
  }

  if ( $header_image_style === 'mask-grunge-frame-a-small' ) {
    $css .= fictioneer_get_customizer_css_snippet( 'header-image-style-grunge-frame-a-small' );
  }

  // --- Fading header image ---------------------------------------------------

  $header_image_fading_start = fictioneer_sanitize_integer( get_theme_mod( 'header_image_fading_start', 0 ), 0, 0, 99 );
  $header_image_fading_breakpoint = (int) get_theme_mod( 'header_image_fading_breakpoint', 0 );

  if ( $header_image_fading_start > 0 ) {
    if ( $header_image_fading_breakpoint > 320 ) {
      $css .= "@media only screen and (min-width: {$header_image_fading_breakpoint}px) {
        :root {
          --header-fading-mask-image: " . fictioneer_get_fading_gradient( 100, $header_image_fading_start, 100, 'var(--header-fading-mask-image-rotation, 180deg)' ) . ";
        }
      }";
    } else {
      $css .= ":root {
        --header-fading-mask-image: " . fictioneer_get_fading_gradient( 100, $header_image_fading_start, 100, 'var(--header-fading-mask-image-rotation, 180deg)' ) . ";
      }";
    }

    $css .= '@media only screen and (min-width: 1024px) {
      .inset-header-image .header-background._style-default._fading-bottom._shadow .header-background__wrapper {
        margin-left: 4px;
        margin-right: 4px;
      }
    }';
  }

  // --- Inset header image ----------------------------------------------------

  if ( get_theme_mod( 'inset_header_image' ) ) {
    $css .= fictioneer_get_customizer_css_snippet( 'inset-header-image' );
  }

  // --- Base layout -----------------------------------------------------------

  $hue_offset_dark = (int) get_theme_mod( 'hue_offset', 0 );
  $saturation_offset_dark = (int) get_theme_mod( 'saturation_offset', 0 );
  $lightness_offset_dark = (int) get_theme_mod( 'lightness_offset', 0 );
  $font_saturation_offset_dark = (int) get_theme_mod( 'font_saturation_offset', 0 );
  $font_lightness_offset_dark = (int) get_theme_mod( 'font_lightness_offset', 0 );
  $hue_offset_light = (int) get_theme_mod( 'hue_offset_light', 0 );
  $saturation_offset_light = (int) get_theme_mod( 'saturation_offset_light', 0 );
  $lightness_offset_light = (int) get_theme_mod( 'lightness_offset_light', 0 );
  $font_saturation_offset_light = (int) get_theme_mod( 'font_saturation_offset_light', 0 );
  $font_lightness_offset_light = (int) get_theme_mod( 'font_lightness_offset_light', 0 );
  $site_width = (int) get_theme_mod( 'site_width', FICTIONEER_DEFAULT_SITE_WIDTH );
  $main_offset = (int) get_theme_mod( 'main_offset', 0 );
  $sidebar_width = (int) get_theme_mod( 'sidebar_width', 256 );
  $sidebar_gap = (int) get_theme_mod( 'sidebar_gap', 48 );
  $logo_min_height = (int) get_theme_mod( 'logo_min_height', 210 );
  $logo_max_height = (int) get_theme_mod( 'logo_height', 210 );
  $title_min = (int) get_theme_mod( 'site_title_font_size_min', 32 );
  $title_max = (int) get_theme_mod( 'site_title_font_size_max', 60 );
  $tagline_min = (int) get_theme_mod( 'site_tagline_font_size_min', 13 );
  $tagline_max = (int) get_theme_mod( 'site_tagline_font_size_max', 18 );
  $header_image_min = (int) get_theme_mod( 'header_image_height_min', 210 );
  $header_image_max = (int) get_theme_mod( 'header_image_height_max', 480 );
  $header_bg_color_light = fictioneer_get_theme_color( 'header_color_light', '' );
  $header_bg_color_dark = fictioneer_get_theme_color( 'header_color_dark', '' );
  $header_min = (int) get_theme_mod( 'header_height_min', 190 );
  $header_max = (int) get_theme_mod( 'header_height_max', 380 );
  $story_cover_width_offset = (int) get_theme_mod( 'story_cover_width_offset', 0 );
  $story_cover_box_shadow = get_theme_mod( 'story_cover_shadow', 'var(--box-shadow-xl)' );
  $card_grid_column_min = (int) get_theme_mod( 'card_grid_column_min', 308 );
  $card_cover_width_mod = get_theme_mod( 'card_cover_width_mod', 1 );
  $card_grid_column_gap_mod = get_theme_mod( 'card_grid_column_gap_mod', 1 );
  $card_grid_row_gap_mod = get_theme_mod( 'card_grid_row_gap_mod', 1 );
  $card_font_size_min_mod = get_theme_mod( 'card_font_size_min_mod', 0 );
  $card_font_size_grow_mod = get_theme_mod( 'card_font_size_grow_mod', 0 );
  $card_font_size_max_mod = get_theme_mod( 'card_font_size_max_mod', 0 );
  $card_box_shadow = get_theme_mod( 'card_shadow', 'var(--box-shadow-m)' );
  $font_primary = fictioneer_get_custom_font( 'primary_font_family_value', 'var(--ff-system)', 'Open Sans' );
  $font_secondary = fictioneer_get_custom_font( 'secondary_font_family_value', 'var(--ff-base)', 'Lato' );
  $font_heading = fictioneer_get_custom_font( 'heading_font_family_value', 'var(--ff-base)', 'Open Sans' );
  $font_site_title = fictioneer_get_custom_font( 'site_title_font_family_value', 'var(--ff-heading)', 'default' );
  $font_nav_item = fictioneer_get_custom_font( 'nav_item_font_family_value', 'var(--ff-base)', 'default' );
  $font_story_title = fictioneer_get_custom_font( 'story_title_font_family_value', 'var(--ff-heading)', 'default' );
  $font_chapter_title = fictioneer_get_custom_font( 'chapter_title_font_family_value', 'var(--ff-heading)', 'default' );
  $font_chapter_list_title = fictioneer_get_custom_font( 'chapter_list_title_font_family_value', 'var(--ff-base)', 'default' );
  $font_card_title = fictioneer_get_custom_font( 'card_title_font_family_value', 'var(--ff-heading)', 'default' );
  $font_card_body = fictioneer_get_custom_font( 'card_body_font_family_value', 'var(--ff-note)', 'default' );
  $font_card_list_link = fictioneer_get_custom_font( 'card_list_link_font_family_value', 'var(--ff-note)', 'default' );

  $dark_shade = fictioneer_hex_to_rgb( get_theme_mod( 'dark_shade', '000000' ) );
  $dark_shade = is_array( $dark_shade ) ? $dark_shade : [0, 0, 0];

  if ( $logo_min_height < $logo_max_height ) {
    $logo_height = fictioneer_get_css_clamp( $logo_min_height, $logo_max_height, 320, $site_width );
  } else {
    $logo_height = $logo_max_height . 'px';
  }

  $css .= ":root {
    --site-width: {$site_width}px;
    --main-offset: {$main_offset}px;
    --sidebar-width: {$sidebar_width}px;
    --sidebar-gap: {$sidebar_gap}px;
    --hue-offset: {$hue_offset_dark}deg;
    --saturation-offset: " . $saturation_offset_dark / 100 . ";
    --lightness-offset: " . $lightness_offset_dark / 100 . ";
    --font-saturation-offset: " . $font_saturation_offset_dark / 100 . ";
    --font-lightness-offset: " . $font_lightness_offset_dark / 100 . ";
    --header-image-height: " . fictioneer_get_css_clamp( $header_image_min, $header_image_max, 320, $site_width ) . ";
    --header-height: calc(" . fictioneer_get_css_clamp( $header_min, $header_max, 320, $site_width ) . " - var(--page-inset-top, 0px));
    --header-logo-height: {$logo_height};
    --header-logo-min-height: {$logo_min_height};
    --header-logo-max-height: {$logo_max_height};
    --site-title-font-size: " . fictioneer_get_css_clamp( $title_min, $title_max, 320, $site_width ) . ";
    --site-title-tagline-font-size: " . fictioneer_get_css_clamp( $tagline_min, $tagline_max, 320, $site_width ) . ";
    --grid-columns-min: {$card_grid_column_min}px;
    --grid-columns-row-gap-multiplier: {$card_grid_row_gap_mod};
    --grid-columns-col-gap-multiplier: {$card_grid_column_gap_mod};
    --card-font-size-min-mod: {$card_font_size_min_mod}px;
    --card-font-size-grow-mod: {$card_font_size_grow_mod}px;
    --card-font-size-max-mod: {$card_font_size_max_mod}px;
    --ff-base: {$font_primary};
    --ff-note: {$font_secondary};
    --ff-heading: {$font_heading};
    --ff-site-title: {$font_site_title};
    --ff-story-title: {$font_story_title};
    --ff-chapter-title: {$font_chapter_title};
    --ff-chapter-list-title: {$font_chapter_list_title};
    --ff-card-title: {$font_card_title};
    --ff-card-body: {$font_card_body};
    --ff-card-list-link: {$font_card_list_link};
    --ff-nav-item: {$font_nav_item};
    --card-cover-width-mod: {$card_cover_width_mod};
    --card-box-shadow: {$card_box_shadow};
    --card-drop-shadow: " . str_replace( 'box-', 'drop-', $card_box_shadow ) . ";
    --story-cover-box-shadow: {$story_cover_box_shadow};
    --recommendation-cover-box-shadow: {$story_cover_box_shadow};
    --floating-cover-image-width: " . fictioneer_get_css_clamp( 56, 200 + $story_cover_width_offset, 320, 768 ) . ";
    --in-content-cover-image-width: " . fictioneer_get_css_clamp( 100, 200 + $story_cover_width_offset, 375, 768 ) . ";
  }";

  if ( $card_box_shadow === 'none' ) {
    $css .= ".card{box-shadow:none!important;}";
  }

  // Only light mode
  $css .= ":root[data-mode=light] {
    --hue-offset: {$hue_offset_light}deg;
    --saturation-offset: " . $saturation_offset_light / 100 . ";
    --lightness-offset: " . $lightness_offset_light / 100 . ";
    --font-saturation-offset: " . $font_saturation_offset_light / 100 . ";
    --font-lightness-offset: " . $font_lightness_offset_light / 100 . ";
  }";

  // --- Custom layout ---------------------------------------------------------

  if ( $sidebar_style !== 'none' ) {
    $css .= ":root, :root[data-theme=base] {
      --layout-spacing-horizontal: " . fictioneer_get_css_clamp( 20, 48, 480, $site_width ) . ";
      --layout-spacing-horizontal-small: " . fictioneer_get_css_clamp( 10, 20, 320, 400 ) . ";
    }";
  }

  if ( get_theme_mod( 'use_custom_layout', false ) ) {
    $vertical_min = (int) get_theme_mod( 'vertical_spacing_min', 24 );
    $vertical_max = (int) get_theme_mod( 'vertical_spacing_max', 48 );
    $horizontal_min = (int) get_theme_mod( 'horizontal_spacing_min', 20 );
    $horizontal_max = (int) get_theme_mod( 'horizontal_spacing_max', 80 );
    $horizontal_small_min = (int) get_theme_mod( 'horizontal_spacing_small_min', 10 );
    $horizontal_small_max = (int) get_theme_mod( 'horizontal_spacing_small_max', 20 );
    $large_border_radius = (int) get_theme_mod( 'large_border_radius', 4 );
    $small_border_radius = (int) get_theme_mod( 'small_border_radius', 2 );
    $nested_border_radius_multiplier = max( 0, get_theme_mod( 'nested_border_radius_multiplier', 1 ) );
    $content_list_gap = (int) get_theme_mod( 'content_list_gap', 4 );

    $css .= ":root, :root[data-theme=base] {
      --layout-spacing-vertical: " . fictioneer_get_css_clamp( $vertical_min, $vertical_max, 480, $site_width ) . ";
      --layout-spacing-horizontal: " . fictioneer_get_css_clamp( $horizontal_min, $horizontal_max, 480, $site_width, '%' ) . ";
      --layout-spacing-horizontal-small: " . fictioneer_get_css_clamp( $horizontal_small_min, $horizontal_small_max, 320, 400, '%' ) . ";
      --layout-border-radius-large: {$large_border_radius}px;
      --layout-border-radius-small: {$small_border_radius}px;
      --layout-nested-border-radius-multiplier: {$nested_border_radius_multiplier};
      --chapter-list-gap: {$content_list_gap}px;
      --content-list-gap: {$content_list_gap}px;
    }";

    if ( $sidebar_style !== 'none' ) {
      $css .= ".has-sidebar {
        --layout-spacing-horizontal: " . fictioneer_get_css_clamp( $horizontal_min, $horizontal_max, 480, $site_width ) . ";
        --layout-spacing-horizontal-small: " . fictioneer_get_css_clamp( $horizontal_small_min, $horizontal_small_max, 320, 400 ) . ";
      }";
    }
  }

  // --- Dark mode font weight adjustment --------------------------------------

  if ( get_theme_mod( 'dark_mode_font_weight', 'adjusted' ) === 'normal' ) {
    $css .= ":root[data-font-weight=default]:is(html) {
      --font-smoothing-webkit: subpixel-antialiased;
      --font-smoothing-moz: auto;
      --font-weight-normal: 400;
      --font-weight-semi-strong: 600;
      --font-weight-strong: 600;
      --font-weight-medium: 500;
      --font-weight-heading: 700;
      --font-weight-badge: 600;
      --font-weight-post-meta: 400;
      --font-weight-read-ribbon: 700;
      --font-weight-card-label: 600;
      --font-weight-navigation: 400;
      --font-letter-spacing-base: 0em;
    }";
  }

  // --- Dark mode colors ------------------------------------------------------

  $css .= ":root {
    --site-title-heading-color: " . fictioneer_hsl_font_code( fictioneer_get_theme_color( 'dark_header_title_color' ) ) . ";
    --site-title-tagline-color: " . fictioneer_hsl_font_code( fictioneer_get_theme_color( 'dark_header_tagline_color' ) ) . ";
  }";

  if ( $header_bg_color_dark ) {
    $css .= ":root {
      --header-background-color: " . fictioneer_hsl_code( $header_bg_color_dark ) . ";
    }";
  }

  if ( get_theme_mod( 'use_custom_dark_mode', false ) ) {
    $css .= ":root, :root[data-theme=base] {"
      .
      implode( '', array_map( function( $prop ) {
        return "--bg-{$prop}-free: " . fictioneer_hsl_code( fictioneer_get_theme_color( "dark_bg_{$prop}" ), 'free' ) . ';';
      }, ['50', '100', '200', '300', '400', '500', '600', '700', '800', '900', '950'] ) )
      .
      "
      --card-frame-border-color: " . fictioneer_hsl_code( fictioneer_get_theme_color( 'dark_card_frame' ) ) . ";
      --dark-shade-rgb:" . implode( ' ', $dark_shade ) . ";
      --primary-400: " . fictioneer_get_theme_color( 'dark_primary_400' ) . ";
      --primary-500: " . fictioneer_get_theme_color( 'dark_primary_500' ) . ";
      --primary-600: " . fictioneer_get_theme_color( 'dark_primary_600' ) . ";
      --red-400: " . fictioneer_get_theme_color( 'dark_red_400' ) . ";
      --red-500: " . fictioneer_get_theme_color( 'dark_red_500' ) . ";
      --red-600: " . fictioneer_get_theme_color( 'dark_red_600' ) . ";
      --green-400: " . fictioneer_get_theme_color( 'dark_green_400' ) . ";
      --green-500: " . fictioneer_get_theme_color( 'dark_green_500' ) . ";
      --green-600: " . fictioneer_get_theme_color( 'dark_green_600' ) . ";
      --theme-color-base: " . fictioneer_hsl_code( fictioneer_get_theme_color( 'dark_theme_color_base' ), 'values' ) . ";
      --navigation-background: " . fictioneer_hsl_code( fictioneer_get_theme_color( 'dark_navigation_background_sticky' ) ) . ";
      --bookmark-color-alpha: " . fictioneer_get_theme_color( 'dark_bookmark_color_alpha' ) . ";
      --bookmark-color-beta: " . fictioneer_get_theme_color( 'dark_bookmark_color_beta' ) . ";
      --bookmark-color-gamma: " . fictioneer_get_theme_color( 'dark_bookmark_color_gamma' ) . ";
      --bookmark-color-delta: " . fictioneer_get_theme_color( 'dark_bookmark_color_delta' ) . ";
      --bookmark-line: " . fictioneer_get_theme_color( 'dark_bookmark_line_color' ) . ";
      --ins-background: " . fictioneer_get_theme_color( 'dark_ins_background' ) . ";
      --del-background: " . fictioneer_get_theme_color( 'dark_del_background' ) . ";"
      .
      implode( '', array_map( function( $prop ) {
        return "--badge-{$prop}-background: " . fictioneer_get_theme_color( "dark_badge_{$prop}_background" ) . ';';
      }, ['generic', 'moderator', 'admin', 'author', 'supporter', 'override'] ) )
      .
    "}
    :root, :root[data-theme=base], :root .chapter-formatting, :root[data-theme=base] .chapter-formatting {"
      .
      implode( '', array_map( function( $prop ) {
        return "--fg-{$prop}: " . fictioneer_hsl_font_code( fictioneer_get_theme_color( "dark_fg_{$prop}" ) ) . ';';
      }, ['100', '200', '300', '400', '500', '600', '700', '800', '900', '950', 'tinted', 'inverted'] ) )
      .
    "}";
  }

  // --- Light mode colors -----------------------------------------------------

  $css .= ":root[data-mode=light] {
    --site-title-heading-color: " . fictioneer_hsl_font_code( fictioneer_get_theme_color( 'light_header_title_color' ) ) . ";
    --site-title-tagline-color: " . fictioneer_hsl_font_code( fictioneer_get_theme_color( 'light_header_tagline_color' ) ) . ";
  }";

  if ( $header_bg_color_light ) {
    $css .= ":root[data-mode=light] {
      --header-background-color: " . fictioneer_hsl_code( $header_bg_color_light ) . ";
    }";
  }

  if ( get_theme_mod( 'use_custom_light_mode', false ) ) {
    $css .= ":root[data-mode=light] {"
      .
      implode( '', array_map( function( $prop ) {
        return "--bg-{$prop}-free: " . fictioneer_hsl_code( fictioneer_get_theme_color( "light_bg_{$prop}" ), 'free' ) . ';';
      }, ['50', '100', '200', '300', '400', '500', '600', '700', '800', '900', '950'] ) )
      .
      "
      --card-frame-border-color: " . fictioneer_hsl_code( fictioneer_get_theme_color( 'light_card_frame' ) ) . ";
      --primary-400: " . fictioneer_get_theme_color( 'light_primary_400' ) . ";
      --primary-500: " . fictioneer_get_theme_color( 'light_primary_500' ) . ";
      --primary-600: " . fictioneer_get_theme_color( 'light_primary_600' ) . ";
      --red-400: " . fictioneer_get_theme_color( 'light_red_400' ) . ";
      --red-500: " . fictioneer_get_theme_color( 'light_red_500' ) . ";
      --red-600: " . fictioneer_get_theme_color( 'light_red_600' ) . ";
      --green-400: " . fictioneer_get_theme_color( 'light_green_400' ) . ";
      --green-500: " . fictioneer_get_theme_color( 'light_green_500' ) . ";
      --green-600: " . fictioneer_get_theme_color( 'light_green_600' ) . ";
      --theme-color-base: " . fictioneer_hsl_code( fictioneer_get_theme_color( 'light_theme_color_base' ), 'values' ) . ";
      --navigation-background: " . fictioneer_hsl_code( fictioneer_get_theme_color( 'light_navigation_background_sticky' ) ) . ";
      --bookmark-color-alpha: " . fictioneer_get_theme_color( 'light_bookmark_color_alpha' ) . ";
      --bookmark-color-beta: " . fictioneer_get_theme_color( 'light_bookmark_color_beta' ) . ";
      --bookmark-color-gamma: " . fictioneer_get_theme_color( 'light_bookmark_color_gamma' ) . ";
      --bookmark-color-delta: " . fictioneer_get_theme_color( 'light_bookmark_color_delta' ) . ";
      --bookmark-line: " . fictioneer_get_theme_color( 'light_bookmark_line_color' ) . ";
      --ins-background: " . fictioneer_get_theme_color( 'light_ins_background' ) . ";
      --del-background: " . fictioneer_get_theme_color( 'light_del_background' ) . ";"
      .
      implode( '', array_map( function( $prop ) {
        return "--badge-{$prop}-background: " . fictioneer_get_theme_color( "light_badge_{$prop}_background" ) . ';';
      }, ['generic', 'moderator', 'admin', 'author', 'supporter', 'override'] ) )
      .
    "}
    :root[data-mode=light], :root[data-mode=light] .chapter-formatting {"
      .
      implode( '', array_map( function( $prop ) {
        return "--fg-{$prop}: " . fictioneer_hsl_font_code( fictioneer_get_theme_color( "light_fg_{$prop}" ) ) . ';';
      }, ['100', '200', '300', '400', '500', '600', '700', '800', '900', '950', 'tinted', 'inverted'] ) )
      .
    "}";
  }

  // --- Header styles ---------------------------------------------------------

  if ( in_array( $header_style, ['top', 'split'] ) ) {
    $css .= fictioneer_get_customizer_css_snippet( 'header-style-top-split' );
  }

  if ( $header_style === 'wide' ) {
    $css .= fictioneer_get_customizer_css_snippet( 'header-style-wide' );
  }

  if ( $header_style === 'text_center' ) {
    $css .= fictioneer_get_customizer_css_snippet( 'header-style-text-center' );
  }

  if ( $header_style === 'post_content' ) {
    $css .= fictioneer_get_customizer_css_snippet( 'header-style-post-content' );
  }

  // --- Page styles -----------------------------------------------------------

  if ( $page_style === 'polygon-mask-image-battered-ringbook' || $page_style === 'polygon-battered' ) {
    $css .= fictioneer_get_customizer_css_snippet( 'page-style-battered' );
  }

  if ( $page_style === 'polygon-mask-image-battered-ringbook' || $page_style === 'mask-image-ringbook' ) {
    $css .= fictioneer_get_customizer_css_snippet( 'page-style-ringbook' );
  }

  if ( $page_style === 'polygon-chamfered' ) {
    $css .= fictioneer_get_customizer_css_snippet( 'page-style-chamfered' );
  }

  if ( $page_style === 'polygon-interface-a' ) {
    $css .= fictioneer_get_customizer_css_snippet( 'page-style-interface-a' );
  }

  if ( $page_style === 'mask-image-wave-a' ) {
    $css .= fictioneer_get_customizer_css_snippet( 'page-style-wave-a' );
  }

  if ( $page_style === 'mask-image-layered-steps-a' ) {
    $css .= fictioneer_get_customizer_css_snippet( 'page-style-layered-steps-a' );
  }

  if ( $page_style === 'mask-image-layered-peaks-a' ) {
    $css .= fictioneer_get_customizer_css_snippet( 'page-style-layered-peaks-a' );
  }

  if ( $page_style === 'mask-image-grunge-a' ) {
    $css .= fictioneer_get_customizer_css_snippet( 'page-style-grunge-a' );
  }

  if ( $page_style === 'none' ) {
    $css .= '.main__background { display:none !important; }';
  }

  // --- Page shadow -----------------------------------------------------------

  if ( ! get_theme_mod( 'page_shadow', true ) ) {
    $css .= ':root.no-page-shadow {
      --minimal-page-box-shadow: none;
      --page-box-shadow: none;
      --page-drop-shadow: none;
    }';
  }

  // --- Card styles -----------------------------------------------------------

  if ( in_array( $card_style, ['unfolded', 'combined'] ) ) {
    $css .= fictioneer_get_customizer_css_snippet( 'card-style-unfolded-combined' );
  }

  if ( $card_style === 'combined' ) {
    $css .= fictioneer_get_customizer_css_snippet( 'card-style-combined' );
  }

  // --- Card frames -----------------------------------------------------------

  if ( $card_frame !== 'default' ) {
    $css .= ':root:not(.minimal) .card{filter:var(--card-drop-shadow);}';
  }

  if ( in_array( $card_frame, ['stacked_right', 'stacked_left', 'stacked_random'] ) ) {
    $css .= fictioneer_get_customizer_css_snippet( 'card-frame-stacked' );

    if ( $card_frame === 'stacked_left' ) {
      $css .= '.card{--this-rotation-mod:-1;}';
    }
  }

  if ( $card_frame === 'border_2px' ) {
    $css .= ':root:not(.minimal) .card{--card-style-border-width: 2px;box-shadow: 0 0 0 var(--card-frame-border-thickness, 2px) var(--card-frame-border-color);}';
  }

  if ( $card_frame === 'border_3px' ) {
    $css .= ':root:not(.minimal) .card{--card-style-border-width: 3px;box-shadow: 0 0 0 var(--card-frame-border-thickness, 3px) var(--card-frame-border-color);}';
  }

  if ( $card_frame === 'chamfered' ) {
    $css .= fictioneer_get_customizer_css_snippet( 'card-frame-chamfered' );
  }

  if ( $card_frame === 'battered' ) {
    $css .= fictioneer_get_customizer_css_snippet( 'card-frame-battered' );
  }

  // --- Content list style ----------------------------------------------------

  if ( $content_list_style === 'full' ) {
    $css .= fictioneer_get_customizer_css_snippet( 'content-list-style-full' );
  }

  if ( $content_list_style === 'free' ) {
    $css .= fictioneer_get_customizer_css_snippet( 'content-list-style-free' );
  }

  if ( $content_list_style === 'lines' ) {
    $css .= fictioneer_get_customizer_css_snippet( 'content-list-style-lines' );
  }

  // --- Footer style ----------------------------------------------------------

  if ( $footer_style === 'isolated' ) {
    $css .= fictioneer_get_customizer_css_snippet( 'footer-style-isolated' );
  }

  // --- Filters ---------------------------------------------------------------

  $css = apply_filters( 'fictioneer_filter_pre_build_customize_css', $css );

  // --- Minify ----------------------------------------------------------------

  $css = fictioneer_minify_css( $css );

  // --- Update options --------------------------------------------------------

  if ( $context !== 'preview' ) {
    update_option( 'fictioneer_customize_css_timestamp', time(), true );
  }

  // --- Save ------------------------------------------------------------------

  file_put_contents( $file_path, $css );
}

/**
 * Builds customization stylesheet
 *
 * @since 5.11.0
 *
 * @param string|null $context  Optional. In which context the stylesheet created,
 *                              for example 'preview' for the Customizer.
 */


function fictioneer_save_word_count( $post_id ) {
  // Prevent multi-fire
  if ( fictioneer_multi_save_guard( $post_id ) ) {
    return;
  }

  // Count
  $word_count = fictioneer_count_words( $post_id );

  // Save
  update_post_meta( $post_id, '_word_count', $word_count );
}


function fictioneer_maintenance_mode() {
  if ( get_option( 'fictioneer_enable_maintenance_mode' ) && ! is_customize_preview() ) {
    if ( ! current_user_can( 'edit_themes' ) || ! is_user_logged_in() ) {
      $note = get_option( 'fictioneer_phrase_maintenance' );
      $note = ! empty( $note ) ? $note : __( 'Website under planned maintenance. Please check back later.', 'fictioneer' );

      wp_die( __( '
Under Maintenance

', 'fictioneer' ) . $note );
    }
  }
}


function fictioneer_is_commenting_disabled( $post_id = null ) {
    // Setup
    $post_id = $post_id ?? get_the_ID();

    // Return immediately if...
    if (
      get_option( 'fictioneer_disable_commenting' ) ||
      get_post_meta( $post_id, 'fictioneer_disable_commenting', true )
    ) {
      return true;
    }

    // Check parent story if chapter...
    if ( get_post_type( $post_id ) === 'fcn_chapter' ) {
      $story_id = fictioneer_get_chapter_story_id( $post_id );

      if ( $story_id ) {
        return get_post_meta( $story_id, 'fictioneer_disable_commenting', true ) == true;
      }
    }

    return false;
  }

/**
   * Check whether commenting is disabled
   *
   * Differs from comments_open() in the regard that it does not hide the whole
   * comment section but does not allow new comments to be posted.
   *
   * @since 5.0.0
   *
   * @param int|null $post_id  Post ID the comments are for. Defaults to current post ID.
   *
   * @return boolean True or false.
   */


function fictioneer_settings_capability_card( $title, $caps, $role ) {
  // Start HTML ---> ?>

<?php echo $title; ?>

          <?php
            foreach ( $caps as $cap ) {
              $role_caps = $role['capabilities'];
              $set = in_array( $cap, $role_caps ) && ( $role_caps[ $cap ] ?? 0 );
              $name = fcntr_admin( $cap );

              if ( empty( $name ) ) {
                $name = str_replace( '_', ' ', $cap );
                $name = str_replace( 'fcn ', '', $name );
                $name = ucwords( $name );

                // Special cases
                $name = $name == 'Unfiltered Html' ? 'Unfiltered HTML' : $name;
                $name = str_replace( 'Recommendations', 'Recommend.', $name );
                $name = str_replace( 'Custom Css', 'Custom CSS', $name );
                $name = str_replace( 'Seo Meta', 'SEO Meta', $name );
                $name = str_replace( 'Custom Page Css', 'Custom Page CSS', $name );
                $name = str_replace( 'Custom Epub Css', 'Custom ePUB CSS', $name );
                $name = str_replace( 'Custom Epub Upload', 'Custom ePUB Upload', $name );
              }

              fictioneer_settings_capability_checkbox( $cap, $name, $set );
            }
          ?>

  <?php // <--- End HTML
}

// =============================================================================
// SETTINGS CONTENT HELPERS
// =============================================================================
/**
 * Renders a role settings capability card
 *
 * @since 5.6.0
 *
 * @param string $title  The title of the card.
 * @param array  $caps   An array of capabilities.
 * @param array  $role   The WP_Role or equivalent array for the role being checked.
 */


function fictioneer_tools_duplicate_story_tags_to_genres() {
  // Verify request
  fictioneer_verify_admin_action( 'fictioneer_duplicate_story_tags_to_genres' );

  // Relay
  fictioneer_convert_taxonomies( 'fcn_story', 'fcn_genre', 'post_tag', true );

  // Log
  fictioneer_log( __( 'Story tags duplicated as genres.', 'fictioneer' ) );

  // Finish
  fictioneer_finish_admin_action( 'fictioneer-duplicate-tags-to-genres' );
}


function fictioneer_delete_null_option( $option, $old_value, $value ) {
  // Theme option?
  if (
    ! array_key_exists( $option, FICTIONEER_OPTIONS['booleans'] ) &&
    ! array_key_exists( $option, FICTIONEER_OPTIONS['strings'] )
  ) {
    return;
  }

  // Delete empty options
  if ( empty( $value ) ) {
    delete_option( $option );
  }
}


public function handle(): void
    {
        collect()
            ->tap(function (): void {
                $this->createProcess(['git', 'rev-parse', '--is-inside-work-tree'])->mustRun();
            })
            ->tap(function () use (&$cachedDiff): void {
                $cachedDiff = $this->option('diff') ?: $this->createProcess($this->diffCommand())->mustRun()->getOutput();
                if (empty($cachedDiff)) {
                    throw new RuntimeException('There are no cached files to commit. Try running `git add` to cache some files.');
                }
            })
            ->tap(function () use (&$type): void {
                $type = $this->choice(
                    'Please choice commit type',
                    $types = $this->configManager->get('types'),
                    array_key_first($types)
                );
            })
            ->tap(function () use ($type, $cachedDiff, &$message): void {
                $message = retry(
                    $this->option('retry-times'),
                    function ($attempts) use ($cachedDiff, $type): string {
                        if ($attempts > 1) {
                            $this->output->note('retrying...');
                        }

                        $originalMessage = $this->generatorManager
                            ->driver($this->option('generator'))
                            ->generate($this->promptFor($cachedDiff, $type));
                        $message = $this->tryFixMessage($originalMessage);
                        if (! str($message)->jsonValidate()) {
                            throw new RuntimeException(sprintf(
                                'The generated commit message(%s) is an invalid JSON.',
                                var_export($originalMessage, true)
                            ));
                        }

                        return $message;
                    },
                    $this->option('retry-sleep'),
                    $this->configManager->get('retry.when')
                );
            })
            ->tap(function () use (&$message): void {
                $message = Collection::json($message)
                    ->map(static function ($content) {
                        if (\is_array($content)) {
                            return collect($content)
                                ->transform(static function (string $line): string {
                                    return (string) str($line)->trim(" \t\n\r\x0B")->start('- ');
                                })
                                ->implode(PHP_EOL);
                        }

                        return $content;
                    })
                    ->tap(function (Collection $message): void {
                        $message = $message->put('', '')->sortKeysUsing(static function (string $a, string $b): int {
                            $rules = ['subject', '', 'body'];

                            return array_search($a, $rules, true) <=> array_search($b, $rules, true);
                        });
                        // $this->table($message->keys()->all(), [$message->all()]);
                        $this->output->horizontalTable($message->keys()->all(), [$message->all()]);
                    })
                    ->tap(function (): void {
                        if (! $this->confirm('Do you want to commit this message?', true)) {
                            $this->output->note('regenerating...');
                            $this->handle();
                        }
                    });
            })
            ->tap(function () use ($message): void {
                if ($this->option('dry-run')) {
                    $this->info($this->hydrateMessage($message));

                    return;
                }

                tap($this->createProcess($this->commitCommandFor($message)), function (Process $process): void {
                    $this->shouldEdit() and $process->setTty(true);
                })->setTimeout(null)->mustRun();
            })
            ->tap(function (): void {
                $this->output->success('Successfully generated and committed message.');
            });
    }

/**
     * @psalm-suppress InvalidArgument
     *
     * @throws \Exception
     */


public function addClassMap(array $classMap)
    {
        if ($this->classMap) {
            $this->classMap = array_merge($this->classMap, $classMap);
        } else {
            $this->classMap = $classMap;
        }
    }

/**
     * @param string[] $classMap Class to filename map
     * @psalm-param array<string, string> $classMap
     *
     * @return void
     */


public static function flatten_blocks( $blocks, $parent_id = null ) {
		$flattened_blocks = [];

		foreach ( $blocks as $block ) {
			// Gather innerBlocks from current block
			$inner_blocks = $block['innerBlocks'] ?? [];
			unset( $block['innerBlocks'] );

			// Set parent ID on current block
			$block['parentId'] = $parent_id;

			// Recurse into inner blocks
			$flattened_blocks[] = $block;
			$flattened_blocks   = array_merge( $flattened_blocks, self::flatten_blocks( $inner_blocks, $block['id'] ) );
		}

		return $flattened_blocks;
	}

/**
	 * Flatten blocks recursively.
	 *
	 * @param array  $blocks the inner blocks in the block.
	 * @param string $parent_id Optional. ID of the parent block that $blocks belong to.
	 *
	 * @return array
	 */


protected function load(): void
    {
        if ($this->loaded) {
            return;
        }

        if (File::exists($this->cachedFolioRoutesPath)) {
            $cache = File::getRequire($this->cachedFolioRoutesPath);

            if (isset($cache['version']) && (int) $cache['version'] === static::$version) {
                $this->routes = $cache['routes'];

                $this->loaded = true;

                return;
            }
        }

        foreach ($this->manager->mountPaths() as $mountPath) {
            $views = Finder::create()->in($mountPath->path)->name('*.blade.php')->files()->getIterator();

            foreach ($views as $view) {
                $matchedView = new MatchedView($view->getRealPath(), [], $mountPath->path);

                if ($name = $matchedView->name()) {
                    $this->routes[$name] = [
                        'mountPath' => Project::relativePathOf($matchedView->mountPath),
                        'path' => Project::relativePathOf($matchedView->path),
                        'baseUri' => $mountPath->baseUri,
                        'domain' => $mountPath->domain,
                    ];
                }
            }
        }

        $this->loaded = true;
    }

/**
     * Load the routes into memory.
     */


public function isArrestedRequestValid(Request $request): bool
    {
        $param = config('requirepin.param', '_uuid');
        $requirePin = RequirePin::where('user_id', Auth::guard(config('requirepin.auth_guard', 'web'))->user()->id)
                        ->where('route_arrested', $request->path())
                        ->where('uuid', $request->{$param})
                        ->whereNull('approved_at')
                        ->whereNull('cancelled_at')
                        ->first();

        if (!isset($requirePin->id)) {
            return false;
        }

        return true;
    }

/**
     * Error Response For Pin Authentication.
     *
     * @param \Illuminate\Http\Request $request
     *
     * @return bool
     */


private static function image($frame, $pixelPerPoint = 4, $outerFrame = 4)
        {
            $h = count($frame);
            $w = strlen($frame[0]);

            $imgW = $w + 2*$outerFrame;
            $imgH = $h + 2*$outerFrame;

            $base_image =ImageCreate($imgW, $imgH);

            $col[0] = ImageColorAllocate($base_image,255,255,255);
            $col[1] = ImageColorAllocate($base_image,0,0,0);

            imagefill($base_image, 0, 0, $col[0]);

            for($y=0; $y<$h; $y++) {
                for($x=0; $x<$w; $x++) {
                    if ($frame[$y][$x] == '1') {
                        ImageSetPixel($base_image,$x+$outerFrame,$y+$outerFrame,$col[1]);
                    }
                }
            }

            $target_image =ImageCreate($imgW * $pixelPerPoint, $imgH * $pixelPerPoint);
            ImageCopyResized($target_image, $base_image, 0, 0, 0, 0, $imgW * $pixelPerPoint, $imgH * $pixelPerPoint, $imgW, $imgH);
            ImageDestroy($base_image);

            return $target_image;
        }


public function add_header($key, $value)
    {
        $this->request_headers[$key] = $value;
        return $this;
    }

/**
     * Add a custom HTTP header to the cURL request.
     *
     * @param string $key (Required) The custom HTTP header to set.
     * @param mixed $value (Required) The value to assign to the custom HTTP header.
     * @return $this A reference to the current instance.
     */


public function set_curlopts($curlopts)
    {
        $this->curlopts = $curlopts;
        return $this;
    }

/**
     * Set additional CURLOPT settings. These will merge with the default settings, and override if
     * there is a duplicate.
     *
     * @param array $curlopts (Optional) A set of key-value pairs that set `CURLOPT` options. These will merge with the existing CURLOPTs, and ones passed here will override the defaults. Keys should be the `CURLOPT_*` constants, not strings.
     * @return $this A reference to the current instance.
     */


public function __construct($sseAlgorithm = null, $kmsMasterKeyID = null)
    {
        $this->sseAlgorithm = $sseAlgorithm;
        $this->kmsMasterKeyID = $kmsMasterKeyID;
    }

/**
     * ServerSideEncryptionConfig constructor.
     * @param null $sseAlgorithm
     * @param null $kmsMasterKeyID
     */


public function parseFromXml($strXml)
    {
        $xml = simplexml_load_string($strXml);
        if (!isset($xml->ApplyServerSideEncryptionByDefault)) return;
        foreach ($xml->ApplyServerSideEncryptionByDefault as $default) {
            foreach ($default as $key => $value) {
                if ($key === 'SSEAlgorithm') {
                    $this->sseAlgorithm = strval($value);
                } elseif ($key === 'KMSMasterKeyID') {
                    $this->kmsMasterKeyID = strval($value);
                }
            }
            break;
        }
    }

/**
     * Parse ServerSideEncryptionConfig from the xml.
     *
     * @param string $strXml
     * @throws OssException
     * @return null
     */


protected function parseDataFromResponse()
    {
        $content = $this->rawResponse->body;
        $config = new CorsConfig();
        $config->parseFromXml($content);
        return $config;
    }


protected function isResponseOk()
    {
        $status = $this->rawResponse->status;
        if ((int)(intval($status) / 100) == 2 || (int)(intval($status)) === 404) {
            return true;
        }
        return false;
    }

/**
     * Judged according to the return HTTP status code, [200-299] that is OK, get the bucket configuration interface,
     * 404 is also considered a valid response
     *
     * @return bool
     */


private function buildCanonicalHeaders()
	{
		$sortMap = array();
		foreach ($this->headers as $headerKey => $headerValue) {
			$key = strtolower($headerKey);
			if (strpos($key, 'x-acs-') === 0) {
				$sortMap[$key] = $headerValue;
			}
		}
		ksort($sortMap);
		$headerString = '';
		foreach ($sortMap as $sortMapKey => $sortMapValue) {
			$headerString = $headerString . $sortMapKey . ':' . $sortMapValue . self::$headerSeparator;
		}
		return $headerString;
	}


public static function getVersion($packageName)
    {
        foreach (self::getInstalled() as $installed) {
            if (!isset($installed['versions'][$packageName])) {
                continue;
            }

            if (!isset($installed['versions'][$packageName]['version'])) {
                return null;
            }

            return $installed['versions'][$packageName]['version'];
        }

        throw new \OutOfBoundsException('Package "' . $packageName . '" is not installed');
    }

/**
     * @param  string      $packageName
     * @return string|null If the package is being replaced or provided but is not really installed, null will be returned as version, use satisfies or getVersionRanges if you need to know if a given version is present
     */


public function testVisitsLocation()
    {
        $location = new JsonLocation();
        $parameter = new Parameter([
            'name'    => 'val',
            'sentAs'  => 'vim',
            'filters' => ['strtoupper']
        ]);
        $response = new Response(200, [], '{"vim":"bar"}');
        $result = new Result();
        $result = $location->before($result, $response, $parameter);
        $result = $location->visit($result, $response, $parameter);
        $this->assertEquals('BAR', $result['val']);
    }

/**
     * @group ResponseLocation
     */


public function request($method, $uri = '', array $options = [])
    {
        $options[RequestOptions::SYNCHRONOUS] = true;
        return $this->requestAsync($method, $uri, $options)->wait();
    }

/**
     * Create and send an HTTP request.
     *
     * Use an absolute path to override the base path of the client, or a
     * relative path to append to the base path of the client. The URL can
     * contain the query string as well.
     *
     * @param string              $method  HTTP method.
     * @param string|UriInterface $uri     URI object or string.
     * @param array               $options Request options to apply. See \GuzzleHttp\RequestOptions.
     *
     * @return ResponseInterface
     * @throws GuzzleException
     */


public function remove($remove)
    {
        $this->cached = null;
        $idx = is_callable($remove) ? 0 : 1;
        $this->stack = array_values(array_filter(
            $this->stack,
            function ($tuple) use ($idx, $remove) {
                return $tuple[$idx] !== $remove;
            }
        ));
    }

/**
     * Remove a middleware by instance or name from the stack.
     *
     * @param callable|string $remove Middleware to remove by instance or name.
     */


private function getHeaders(array $headers)
    {
        $str = '';
        foreach ($headers as $key => $value) {
            $str .= "{$key}: {$value}\r\n";
        }

        return "--{$this->boundary}\r\n" . trim($str) . "\r\n\r\n";
    }

/**
     * Get the headers needed before transferring the content of a POST file
     */


public function render()
    {
        return view('component', [
            'foo' => 'bar',
        ]);
    }

/**
     * @return Closure|\Illuminate\Contracts\View\View|string
     */


protected function configureRateLimiting()
    {
        RateLimiter::for('api', function (Request $request) {
            return Limit::perMinute(60)->by($request->user()?->id ?: $request->ip());
        });
    }

/**
     * Configure the rate limiters for the application.
     *
     * @return void
     */


public function rules(): array
    {
        return [
            'name' => ['string', 'max:255'],
            'email' => ['email', 'max:255', Rule::unique(User::class)->ignore($this->user()->id)],
        ];
    }

/**
     * Get the validation rules that apply to the request.
     *
     * @return array<string, \Illuminate\Contracts\Validation\Rule|array|string>
     */


public static function basePath($path = '')
        {
                        /** @var \Illuminate\Foundation\Application $instance */
                        return $instance->basePath($path);
        }

/**
         * Get the base path of the Laravel installation.
         *
         * @param string $path
         * @return string
         * @static
         */


public static function makeWith($abstract, $parameters = [])
        {            //Method inherited from \Illuminate\Container\Container
                        /** @var \Illuminate\Foundation\Application $instance */
                        return $instance->makeWith($abstract, $parameters);
        }

/**
         * An alias function name for make().
         *
         * @param string|callable $abstract
         * @param array $parameters
         * @return mixed
         * @throws \Illuminate\Contracts\Container\BindingResolutionException
         * @static
         */


public static function all()
        {            //Method inherited from \Illuminate\Foundation\Console\Kernel
                        /** @var \App\Console\Kernel $instance */
                        return $instance->all();
        }

/**
         * Get all of the commands registered with the console.
         *
         * @return array
         * @static
         */


public static function inspect($ability, $arguments = [])
        {
                        /** @var \Illuminate\Auth\Access\Gate $instance */
                        return $instance->inspect($ability, $arguments);
        }

/**
         * Inspect the user for the given ability.
         *
         * @param string $ability
         * @param array|mixed $arguments
         * @return \Illuminate\Auth\Access\Response
         * @static
         */


public static function queued($mailable, $callback = null)
        {
                        /** @var \Illuminate\Support\Testing\Fakes\MailFake $instance */
                        return $instance->queued($mailable, $callback);
        }

/**
         * Get all of the queued mailables matching a truth-test callback.
         *
         * @param string|\Closure $mailable
         * @param callable|null $callback
         * @return \Illuminate\Support\Collection
         * @static
         */


public static function getMimeTypes($format)
        {            //Method inherited from \Symfony\Component\HttpFoundation\Request
                        return \Illuminate\Http\Request::getMimeTypes($format);
        }

/**
         * Gets the mime types associated with the format.
         *
         * @return string[]
         * @static
         */


public static function action($action, $parameters = [], $absolute = true)
        {
                        /** @var \Illuminate\Routing\UrlGenerator $instance */
                        return $instance->action($action, $parameters, $absolute);
        }

/**
         * Get the URL to a controller action.
         *
         * @param string|array $action
         * @param mixed $parameters
         * @param bool $absolute
         * @return string
         * @throws \InvalidArgumentException
         * @static
         */


final private function natlangElementYear($elem)
    {
        if (! $elem['hasInterval']) {
            return $elem['number1'];
        }

        $txt = $this->natlangApply('elemYear: every_consecutive_year'.($elem['interval'] == 1 ? '' : '_plural'), $elem['interval']);
        if (($elem['number1'] != $this->_cronMonths['rangeMin']) || ($elem['number2'] != $this->_cronMonths['rangeMax'])) {
            $txt .= ' ('.$this->natlangApply('elemYear: from_X_through_Y', $elem['number1'], $elem['number2']).')';
        }

        return $txt;
    }

//
// Function:    natlangElementYear
//
// Description:    Converts an entry from the year specification to natural language.
//


public function up()
    {
        Schema::table('activity_log', function (Blueprint $table) {
            $table->uuid('batch_uuid')->nullable()->after('properties');
        });
        Schema::table('activity_log_archives', function (Blueprint $table) {
            $table->uuid('batch_uuid')->nullable()->after('properties');
        });
    }

/**
     * Run the migrations.
     *
     * @return void
     */


public function create(array $queryParams = []): Response
    {
        return $this->client->execute(
            $this->createRequest('POST', '/snapshots' . $this->queryBuild($queryParams))
        );
    }

/**
     * # Create storage snapshot
     * Create new snapshot of the whole storage
     *
     * @throws InvalidArgumentException
     */


public function detail(int $id)
    {
        $goods = Goods::query()
            ->with(['coupon','goods_sub'])
            ->withCount(['carmis' => function($query) {
                $query->where('status', Carmis::STATUS_UNSOLD);
            }])->where('id', $id)->first();
        return $goods;
    }

/**
     * 商品详情
     *
     * @param int $id 商品id
     * @return \Illuminate\Database\Eloquent\Builder|\Illuminate\Database\Eloquent\Model|object|null
     *
     * @author    assimon<ashang@utf8.hk>
     * @copyright assimon<ashang@utf8.hk>
     * @link      http://utf8.hk/
     */


public function getId(): string
    {
        return 'filament-jobs-monitor';
    }

/**
     * Get the plugin identifier.
     */


public function expired(): self
    {
        return $this->state([
            'status' => Subscription::STATUS_EXPIRED,
        ]);
    }

/**
     * Mark the subscription as expired
     */


public function load(): array
    {
        $text = file_get_contents($this->filePath->getRealPath());
        $metadata = ['source' => $this->filePath->getRealPath()];
        return [new Document(pageContent:$text, metadata: $metadata)];
    }

/**
     * Load from file path.
     *
     * @return Document[]
     */


protected static function newFactory(): CommentFactory
    {
        return CommentFactory::new();
    }

/**
     * @return CommentFactory
     */