diff --git a/assets/css/search.css b/assets/css/search.css
new file mode 100644
index 00000000..d788cd22
--- /dev/null
+++ b/assets/css/search.css
@@ -0,0 +1,40 @@
+.searchbox input {
+ padding: 4px 10px;
+ width: 100%;
+ color: var(--primary);
+ font-weight: bold;
+ border: 2px solid var(--tertiary);
+ border-radius: var(--radius);
+}
+
+.searchbox input:focus {
+ border-color: var(--secondary);
+}
+
+#searchResults li {
+ list-style: none;
+ border-radius: var(--radius);
+ padding: 10px;
+ margin: 10px 0;
+ position: relative;
+ font-weight: 500;
+}
+
+#searchResults {
+ margin: 10px 0;
+ width: 100%;
+}
+
+#searchResults li:active {
+ transition: transform .1s;
+ transform: scale(.98);
+}
+
+#searchResults a {
+ position: absolute;
+ width: 100%;
+ height: 100%;
+ top: 0px;
+ left: 0px;
+ outline: none;
+}
diff --git a/assets/js/fastsearch.js b/assets/js/fastsearch.js
new file mode 100644
index 00000000..3261c155
--- /dev/null
+++ b/assets/js/fastsearch.js
@@ -0,0 +1,57 @@
+var fuse; // holds our search engine
+
+// load our search index, only executed onload
+function loadSearch() {
+ var xhr = new XMLHttpRequest();
+ xhr.onreadystatechange = function () {
+ if (xhr.readyState === 4) {
+ if (xhr.status === 200) {
+ var data = JSON.parse(xhr.responseText);
+ if (data) {
+ // fuse.js options; check fuse.js website for details
+ var options = {
+ isCaseSensitive: false,
+ shouldSort: true,
+ location: 0,
+ distance: 100,
+ threshold: 0.4,
+ minMatchCharLength: 0,
+ keys: [
+ 'title',
+ 'permalink',
+ 'summary',
+ 'content'
+ ]
+ };
+ fuse = new Fuse(data, options); // build the index from the json file
+ }
+ } else {
+ console.log(xhr.responseText);
+ }
+ }
+ };
+ xhr.open('GET', "../index.json");
+ xhr.send();
+}
+
+// execute search as each character is typed
+document.getElementById("searchInput").onkeyup = function (e) {
+ // run a search query (for "term") every time a letter is typed
+ // in the search box
+ const results = fuse.search(this.value); // the actual query being run using fuse.js
+
+ if (results.length !== 0) {
+ // build our html if result exists
+ let resultSet = ''; // our results bucket
+
+ for (let item in results) {
+ resultSet = resultSet + itemGen(results[item].item.title, results[item].item.permalink)
+ }
+
+ document.getElementById("searchResults").innerHTML = resultSet;
+ }
+}
+
+function itemGen(name, link) {
+ return `
`
+}
diff --git a/assets/js/fuse.js b/assets/js/fuse.js
new file mode 100644
index 00000000..a037a879
--- /dev/null
+++ b/assets/js/fuse.js
@@ -0,0 +1,2252 @@
+/**
+ * Fuse.js v6.4.3 - Lightweight fuzzy-search (http://fusejs.io)
+ *
+ * Copyright (c) 2020 Kiro Risk (http://kiro.me)
+ * All Rights Reserved. Apache Software License 2.0
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ */
+
+(function (global, factory) {
+ typeof exports === 'object' && typeof module !== 'undefined' ? module.exports = factory() :
+ typeof define === 'function' && define.amd ? define(factory) :
+ (global = global || self, global.Fuse = factory());
+}(this, (function () { 'use strict';
+
+ function _typeof(obj) {
+ "@babel/helpers - typeof";
+
+ if (typeof Symbol === "function" && typeof Symbol.iterator === "symbol") {
+ _typeof = function (obj) {
+ return typeof obj;
+ };
+ } else {
+ _typeof = function (obj) {
+ return obj && typeof Symbol === "function" && obj.constructor === Symbol && obj !== Symbol.prototype ? "symbol" : typeof obj;
+ };
+ }
+
+ return _typeof(obj);
+ }
+
+ function _classCallCheck(instance, Constructor) {
+ if (!(instance instanceof Constructor)) {
+ throw new TypeError("Cannot call a class as a function");
+ }
+ }
+
+ function _defineProperties(target, props) {
+ for (var i = 0; i < props.length; i++) {
+ var descriptor = props[i];
+ descriptor.enumerable = descriptor.enumerable || false;
+ descriptor.configurable = true;
+ if ("value" in descriptor) descriptor.writable = true;
+ Object.defineProperty(target, descriptor.key, descriptor);
+ }
+ }
+
+ function _createClass(Constructor, protoProps, staticProps) {
+ if (protoProps) _defineProperties(Constructor.prototype, protoProps);
+ if (staticProps) _defineProperties(Constructor, staticProps);
+ return Constructor;
+ }
+
+ function _defineProperty(obj, key, value) {
+ if (key in obj) {
+ Object.defineProperty(obj, key, {
+ value: value,
+ enumerable: true,
+ configurable: true,
+ writable: true
+ });
+ } else {
+ obj[key] = value;
+ }
+
+ return obj;
+ }
+
+ function ownKeys(object, enumerableOnly) {
+ var keys = Object.keys(object);
+
+ if (Object.getOwnPropertySymbols) {
+ var symbols = Object.getOwnPropertySymbols(object);
+ if (enumerableOnly) symbols = symbols.filter(function (sym) {
+ return Object.getOwnPropertyDescriptor(object, sym).enumerable;
+ });
+ keys.push.apply(keys, symbols);
+ }
+
+ return keys;
+ }
+
+ function _objectSpread2(target) {
+ for (var i = 1; i < arguments.length; i++) {
+ var source = arguments[i] != null ? arguments[i] : {};
+
+ if (i % 2) {
+ ownKeys(Object(source), true).forEach(function (key) {
+ _defineProperty(target, key, source[key]);
+ });
+ } else if (Object.getOwnPropertyDescriptors) {
+ Object.defineProperties(target, Object.getOwnPropertyDescriptors(source));
+ } else {
+ ownKeys(Object(source)).forEach(function (key) {
+ Object.defineProperty(target, key, Object.getOwnPropertyDescriptor(source, key));
+ });
+ }
+ }
+
+ return target;
+ }
+
+ function _inherits(subClass, superClass) {
+ if (typeof superClass !== "function" && superClass !== null) {
+ throw new TypeError("Super expression must either be null or a function");
+ }
+
+ subClass.prototype = Object.create(superClass && superClass.prototype, {
+ constructor: {
+ value: subClass,
+ writable: true,
+ configurable: true
+ }
+ });
+ if (superClass) _setPrototypeOf(subClass, superClass);
+ }
+
+ function _getPrototypeOf(o) {
+ _getPrototypeOf = Object.setPrototypeOf ? Object.getPrototypeOf : function _getPrototypeOf(o) {
+ return o.__proto__ || Object.getPrototypeOf(o);
+ };
+ return _getPrototypeOf(o);
+ }
+
+ function _setPrototypeOf(o, p) {
+ _setPrototypeOf = Object.setPrototypeOf || function _setPrototypeOf(o, p) {
+ o.__proto__ = p;
+ return o;
+ };
+
+ return _setPrototypeOf(o, p);
+ }
+
+ function _isNativeReflectConstruct() {
+ if (typeof Reflect === "undefined" || !Reflect.construct) return false;
+ if (Reflect.construct.sham) return false;
+ if (typeof Proxy === "function") return true;
+
+ try {
+ Date.prototype.toString.call(Reflect.construct(Date, [], function () {}));
+ return true;
+ } catch (e) {
+ return false;
+ }
+ }
+
+ function _assertThisInitialized(self) {
+ if (self === void 0) {
+ throw new ReferenceError("this hasn't been initialised - super() hasn't been called");
+ }
+
+ return self;
+ }
+
+ function _possibleConstructorReturn(self, call) {
+ if (call && (typeof call === "object" || typeof call === "function")) {
+ return call;
+ }
+
+ return _assertThisInitialized(self);
+ }
+
+ function _createSuper(Derived) {
+ var hasNativeReflectConstruct = _isNativeReflectConstruct();
+
+ return function _createSuperInternal() {
+ var Super = _getPrototypeOf(Derived),
+ result;
+
+ if (hasNativeReflectConstruct) {
+ var NewTarget = _getPrototypeOf(this).constructor;
+
+ result = Reflect.construct(Super, arguments, NewTarget);
+ } else {
+ result = Super.apply(this, arguments);
+ }
+
+ return _possibleConstructorReturn(this, result);
+ };
+ }
+
+ function _toConsumableArray(arr) {
+ return _arrayWithoutHoles(arr) || _iterableToArray(arr) || _unsupportedIterableToArray(arr) || _nonIterableSpread();
+ }
+
+ function _arrayWithoutHoles(arr) {
+ if (Array.isArray(arr)) return _arrayLikeToArray(arr);
+ }
+
+ function _iterableToArray(iter) {
+ if (typeof Symbol !== "undefined" && Symbol.iterator in Object(iter)) return Array.from(iter);
+ }
+
+ function _unsupportedIterableToArray(o, minLen) {
+ if (!o) return;
+ if (typeof o === "string") return _arrayLikeToArray(o, minLen);
+ var n = Object.prototype.toString.call(o).slice(8, -1);
+ if (n === "Object" && o.constructor) n = o.constructor.name;
+ if (n === "Map" || n === "Set") return Array.from(o);
+ if (n === "Arguments" || /^(?:Ui|I)nt(?:8|16|32)(?:Clamped)?Array$/.test(n)) return _arrayLikeToArray(o, minLen);
+ }
+
+ function _arrayLikeToArray(arr, len) {
+ if (len == null || len > arr.length) len = arr.length;
+
+ for (var i = 0, arr2 = new Array(len); i < len; i++) arr2[i] = arr[i];
+
+ return arr2;
+ }
+
+ function _nonIterableSpread() {
+ throw new TypeError("Invalid attempt to spread non-iterable instance.\nIn order to be iterable, non-array objects must have a [Symbol.iterator]() method.");
+ }
+
+ function isArray(value) {
+ return !Array.isArray ? getTag(value) === '[object Array]' : Array.isArray(value);
+ } // Adapted from: https://github.com/lodash/lodash/blob/master/.internal/baseToString.js
+
+ var INFINITY = 1 / 0;
+ function baseToString(value) {
+ // Exit early for strings to avoid a performance hit in some environments.
+ if (typeof value == 'string') {
+ return value;
+ }
+
+ var result = value + '';
+ return result == '0' && 1 / value == -INFINITY ? '-0' : result;
+ }
+ function toString(value) {
+ return value == null ? '' : baseToString(value);
+ }
+ function isString(value) {
+ return typeof value === 'string';
+ }
+ function isNumber(value) {
+ return typeof value === 'number';
+ } // Adapted from: https://github.com/lodash/lodash/blob/master/isBoolean.js
+
+ function isBoolean(value) {
+ return value === true || value === false || isObjectLike(value) && getTag(value) == '[object Boolean]';
+ }
+ function isObject(value) {
+ return _typeof(value) === 'object';
+ } // Checks if `value` is object-like.
+
+ function isObjectLike(value) {
+ return isObject(value) && value !== null;
+ }
+ function isDefined(value) {
+ return value !== undefined && value !== null;
+ }
+ function isBlank(value) {
+ return !value.trim().length;
+ } // Gets the `toStringTag` of `value`.
+ // Adapted from: https://github.com/lodash/lodash/blob/master/.internal/getTag.js
+
+ function getTag(value) {
+ return value == null ? value === undefined ? '[object Undefined]' : '[object Null]' : Object.prototype.toString.call(value);
+ }
+
+ var EXTENDED_SEARCH_UNAVAILABLE = 'Extended search is not available';
+ var INCORRECT_INDEX_TYPE = "Incorrect 'index' type";
+ var LOGICAL_SEARCH_INVALID_QUERY_FOR_KEY = function LOGICAL_SEARCH_INVALID_QUERY_FOR_KEY(key) {
+ return "Invalid value for key ".concat(key);
+ };
+ var PATTERN_LENGTH_TOO_LARGE = function PATTERN_LENGTH_TOO_LARGE(max) {
+ return "Pattern length exceeds max of ".concat(max, ".");
+ };
+ var MISSING_KEY_PROPERTY = function MISSING_KEY_PROPERTY(name) {
+ return "Missing ".concat(name, " property in key");
+ };
+ var INVALID_KEY_WEIGHT_VALUE = function INVALID_KEY_WEIGHT_VALUE(key) {
+ return "Property 'weight' in key '".concat(key, "' must be a positive integer");
+ };
+
+ var hasOwn = Object.prototype.hasOwnProperty;
+
+ var KeyStore = /*#__PURE__*/function () {
+ function KeyStore(keys) {
+ var _this = this;
+
+ _classCallCheck(this, KeyStore);
+
+ this._keys = [];
+ this._keyMap = {};
+ var totalWeight = 0;
+ keys.forEach(function (key) {
+ var obj = createKey(key);
+ totalWeight += obj.weight;
+
+ _this._keys.push(obj);
+
+ _this._keyMap[obj.id] = obj;
+ totalWeight += obj.weight;
+ }); // Normalize weights so that their sum is equal to 1
+
+ this._keys.forEach(function (key) {
+ key.weight /= totalWeight;
+ });
+ }
+
+ _createClass(KeyStore, [{
+ key: "get",
+ value: function get(keyId) {
+ return this._keyMap[keyId];
+ }
+ }, {
+ key: "keys",
+ value: function keys() {
+ return this._keys;
+ }
+ }, {
+ key: "toJSON",
+ value: function toJSON() {
+ return JSON.stringify(this._keys);
+ }
+ }]);
+
+ return KeyStore;
+ }();
+ function createKey(key) {
+ var path = null;
+ var id = null;
+ var src = null;
+ var weight = 1;
+
+ if (isString(key) || isArray(key)) {
+ src = key;
+ path = createKeyPath(key);
+ id = createKeyId(key);
+ } else {
+ if (!hasOwn.call(key, 'name')) {
+ throw new Error(MISSING_KEY_PROPERTY('name'));
+ }
+
+ var name = key.name;
+ src = name;
+
+ if (hasOwn.call(key, 'weight')) {
+ weight = key.weight;
+
+ if (weight <= 0) {
+ throw new Error(INVALID_KEY_WEIGHT_VALUE(name));
+ }
+ }
+
+ path = createKeyPath(name);
+ id = createKeyId(name);
+ }
+
+ return {
+ path: path,
+ id: id,
+ weight: weight,
+ src: src
+ };
+ }
+ function createKeyPath(key) {
+ return isArray(key) ? key : key.split('.');
+ }
+ function createKeyId(key) {
+ return isArray(key) ? key.join('.') : key;
+ }
+
+ function get(obj, path) {
+ var list = [];
+ var arr = false;
+
+ var deepGet = function deepGet(obj, path, index) {
+ if (!isDefined(obj)) {
+ return;
+ }
+
+ if (!path[index]) {
+ // If there's no path left, we've arrived at the object we care about.
+ list.push(obj);
+ } else {
+ var key = path[index];
+ var value = obj[key];
+
+ if (!isDefined(value)) {
+ return;
+ } // If we're at the last value in the path, and if it's a string/number/bool,
+ // add it to the list
+
+
+ if (index === path.length - 1 && (isString(value) || isNumber(value) || isBoolean(value))) {
+ list.push(toString(value));
+ } else if (isArray(value)) {
+ arr = true; // Search each item in the array.
+
+ for (var i = 0, len = value.length; i < len; i += 1) {
+ deepGet(value[i], path, index + 1);
+ }
+ } else if (path.length) {
+ // An object. Recurse further.
+ deepGet(value, path, index + 1);
+ }
+ }
+ }; // Backwards compatibility (since path used to be a string)
+
+
+ deepGet(obj, isString(path) ? path.split('.') : path, 0);
+ return arr ? list : list[0];
+ }
+
+ var MatchOptions = {
+ // Whether the matches should be included in the result set. When `true`, each record in the result
+ // set will include the indices of the matched characters.
+ // These can consequently be used for highlighting purposes.
+ includeMatches: false,
+ // When `true`, the matching function will continue to the end of a search pattern even if
+ // a perfect match has already been located in the string.
+ findAllMatches: false,
+ // Minimum number of characters that must be matched before a result is considered a match
+ minMatchCharLength: 1
+ };
+ var BasicOptions = {
+ // When `true`, the algorithm continues searching to the end of the input even if a perfect
+ // match is found before the end of the same input.
+ isCaseSensitive: false,
+ // When true, the matching function will continue to the end of a search pattern even if
+ includeScore: false,
+ // List of properties that will be searched. This also supports nested properties.
+ keys: [],
+ // Whether to sort the result list, by score
+ shouldSort: true,
+ // Default sort function: sort by ascending score, ascending index
+ sortFn: function sortFn(a, b) {
+ return a.score === b.score ? a.idx < b.idx ? -1 : 1 : a.score < b.score ? -1 : 1;
+ }
+ };
+ var FuzzyOptions = {
+ // Approximately where in the text is the pattern expected to be found?
+ location: 0,
+ // At what point does the match algorithm give up. A threshold of '0.0' requires a perfect match
+ // (of both letters and location), a threshold of '1.0' would match anything.
+ threshold: 0.6,
+ // Determines how close the match must be to the fuzzy location (specified above).
+ // An exact letter match which is 'distance' characters away from the fuzzy location
+ // would score as a complete mismatch. A distance of '0' requires the match be at
+ // the exact location specified, a threshold of '1000' would require a perfect match
+ // to be within 800 characters of the fuzzy location to be found using a 0.8 threshold.
+ distance: 100
+ };
+ var AdvancedOptions = {
+ // When `true`, it enables the use of unix-like search commands
+ useExtendedSearch: false,
+ // The get function to use when fetching an object's properties.
+ // The default will search nested paths *ie foo.bar.baz*
+ getFn: get,
+ // When `true`, search will ignore `location` and `distance`, so it won't matter
+ // where in the string the pattern appears.
+ // More info: https://fusejs.io/concepts/scoring-theory.html#fuzziness-score
+ ignoreLocation: false,
+ // When `true`, the calculation for the relevance score (used for sorting) will
+ // ignore the field-length norm.
+ // More info: https://fusejs.io/concepts/scoring-theory.html#field-length-norm
+ ignoreFieldNorm: false
+ };
+ var Config = _objectSpread2({}, BasicOptions, {}, MatchOptions, {}, FuzzyOptions, {}, AdvancedOptions);
+
+ var SPACE = /[^ ]+/g; // Field-length norm: the shorter the field, the higher the weight.
+ // Set to 3 decimals to reduce index size.
+
+ function norm() {
+ var mantissa = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : 3;
+ var cache = new Map();
+ return {
+ get: function get(value) {
+ var numTokens = value.match(SPACE).length;
+
+ if (cache.has(numTokens)) {
+ return cache.get(numTokens);
+ }
+
+ var n = parseFloat((1 / Math.sqrt(numTokens)).toFixed(mantissa));
+ cache.set(numTokens, n);
+ return n;
+ },
+ clear: function clear() {
+ cache.clear();
+ }
+ };
+ }
+
+ var FuseIndex = /*#__PURE__*/function () {
+ function FuseIndex() {
+ var _ref = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : {},
+ _ref$getFn = _ref.getFn,
+ getFn = _ref$getFn === void 0 ? Config.getFn : _ref$getFn;
+
+ _classCallCheck(this, FuseIndex);
+
+ this.norm = norm(3);
+ this.getFn = getFn;
+ this.isCreated = false;
+ this.setIndexRecords();
+ }
+
+ _createClass(FuseIndex, [{
+ key: "setSources",
+ value: function setSources() {
+ var docs = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : [];
+ this.docs = docs;
+ }
+ }, {
+ key: "setIndexRecords",
+ value: function setIndexRecords() {
+ var records = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : [];
+ this.records = records;
+ }
+ }, {
+ key: "setKeys",
+ value: function setKeys() {
+ var _this = this;
+
+ var keys = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : [];
+ this.keys = keys;
+ this._keysMap = {};
+ keys.forEach(function (key, idx) {
+ _this._keysMap[key.id] = idx;
+ });
+ }
+ }, {
+ key: "create",
+ value: function create() {
+ var _this2 = this;
+
+ if (this.isCreated || !this.docs.length) {
+ return;
+ }
+
+ this.isCreated = true; // List is Array
+
+ if (isString(this.docs[0])) {
+ this.docs.forEach(function (doc, docIndex) {
+ _this2._addString(doc, docIndex);
+ });
+ } else {
+ // List is Array