aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorFuwn <[email protected]>2020-11-06 18:24:26 -0800
committerFuwn <[email protected]>2020-11-06 18:24:26 -0800
commit9cdce4254700691301608c6f1d3081950023cc4f (patch)
tree9d24529acc19b354f80cb2d610aa1e7686f4d530
downloadblog-9cdce4254700691301608c6f1d3081950023cc4f.tar.xz
blog-9cdce4254700691301608c6f1d3081950023cc4f.zip
repo: initial :star:
-rw-r--r--.gitignore2
-rw-r--r--.jshintrc3
-rw-r--r--.prettierrc6
-rw-r--r--README.md20
-rw-r--r--articles/2020-11-06-welcome.md10
-rw-r--r--articles/programming-languages-code.js180
-rw-r--r--editor/editor.js79
-rw-r--r--editor/public/editor.css30
-rw-r--r--editor/public/editor.js87
-rw-r--r--editor/public/ejs.min.js1
-rw-r--r--editor/public/marked.min.js6
-rw-r--r--editor/public/strftime.min.js23
-rw-r--r--generate-tags.js45
-rw-r--r--hidden/2013-08-05-seriously-the-reflog-isnt-that-scary.md320
-rw-r--r--hidden/2014-06-22-hello-world.md204
-rw-r--r--hidden/2014-12-30-i-paid-29-dollars-for-this-domain.md22
-rw-r--r--hidden/2015-03-17-blogging-is-terrifying.md25
-rw-r--r--hidden/2015-03-17-projects.md28
-rw-r--r--hidden/2015-03-28-clicking-with-a-keyboard.md231
-rw-r--r--hidden/2015-03-30-focus-and-hover-hand-in-hand.md125
-rw-r--r--hidden/2015-04-26-the-imperfect-development-setup.md8
-rw-r--r--hidden/2015-07-28-building-jquery-elements-with-jsx.md38
-rw-r--r--hidden/2015-08-10-reenergizing-my-love-for-web-development.md92
-rw-r--r--hidden/2015-09-07-removing-collaborator-spam-from-your-github-feed.md79
-rw-r--r--hidden/2015-10-15-ask-me-anything.md16
-rw-r--r--hidden/2016-04-24-random-bowling.md30
-rw-r--r--hidden/2016-05-27-color-blindness.md56
-rw-r--r--hidden/2017-08-15-ocaml-microservices.md293
-rw-r--r--hidden/2017-08-19-typesafe-javascript-chaining-with-ocaml-and-bucklescript.md318
-rw-r--r--hidden/2018-01-07-j-fibonacci.md258
-rw-r--r--hidden/2018-02-10-j-pascal.md279
-rw-r--r--hidden/2019-04-13-programming-languages.md511
-rw-r--r--hidden/2019-05-10-infinite-lists.md682
-rw-r--r--hidden/2019-05-18-katex-test.md15
-rw-r--r--hidden/2019-05-19-large-numbers.md469
-rw-r--r--hidden/2020-04-01-j-sorting.md173
-rw-r--r--hidden/2020-04-28-98-dot-css.md72
-rw-r--r--hidden/2020-05-05-mandel.md107
-rw-r--r--hidden/2020-05-22-functions-that-go-backwards.md314
-rw-r--r--index.js42
-rw-r--r--load-article.js75
-rw-r--r--make-tweet-url.js11
-rw-r--r--netlify.toml7
-rw-r--r--package.json46
-rw-r--r--plugins/tweet.js6
-rw-r--r--public/css/bowling.css178
-rw-r--r--public/css/color-blindness.css30
-rw-r--r--public/css/demos/clicking.css46
-rw-r--r--public/css/demos/hover.css120
-rw-r--r--public/css/demos/shared.css28
-rw-r--r--public/css/fonts/KaTeX_AMS-Regular.ttfbin0 -> 70972 bytes
-rw-r--r--public/css/fonts/KaTeX_AMS-Regular.woffbin0 -> 38868 bytes
-rw-r--r--public/css/fonts/KaTeX_AMS-Regular.woff2bin0 -> 32944 bytes
-rw-r--r--public/css/fonts/KaTeX_Caligraphic-Bold.ttfbin0 -> 19316 bytes
-rw-r--r--public/css/fonts/KaTeX_Caligraphic-Bold.woffbin0 -> 11696 bytes
-rw-r--r--public/css/fonts/KaTeX_Caligraphic-Bold.woff2bin0 -> 10448 bytes
-rw-r--r--public/css/fonts/KaTeX_Caligraphic-Regular.ttfbin0 -> 18684 bytes
-rw-r--r--public/css/fonts/KaTeX_Caligraphic-Regular.woffbin0 -> 11460 bytes
-rw-r--r--public/css/fonts/KaTeX_Caligraphic-Regular.woff2bin0 -> 10240 bytes
-rw-r--r--public/css/fonts/KaTeX_Fraktur-Bold.ttfbin0 -> 35660 bytes
-rw-r--r--public/css/fonts/KaTeX_Fraktur-Bold.woffbin0 -> 22632 bytes
-rw-r--r--public/css/fonts/KaTeX_Fraktur-Bold.woff2bin0 -> 20360 bytes
-rw-r--r--public/css/fonts/KaTeX_Fraktur-Regular.ttfbin0 -> 34352 bytes
-rw-r--r--public/css/fonts/KaTeX_Fraktur-Regular.woffbin0 -> 22088 bytes
-rw-r--r--public/css/fonts/KaTeX_Fraktur-Regular.woff2bin0 -> 19784 bytes
-rw-r--r--public/css/fonts/KaTeX_Main-Bold.ttfbin0 -> 60784 bytes
-rw-r--r--public/css/fonts/KaTeX_Main-Bold.woffbin0 -> 35464 bytes
-rw-r--r--public/css/fonts/KaTeX_Main-Bold.woff2bin0 -> 30244 bytes
-rw-r--r--public/css/fonts/KaTeX_Main-BoldItalic.ttfbin0 -> 44496 bytes
-rw-r--r--public/css/fonts/KaTeX_Main-BoldItalic.woffbin0 -> 25352 bytes
-rw-r--r--public/css/fonts/KaTeX_Main-BoldItalic.woff2bin0 -> 21944 bytes
-rw-r--r--public/css/fonts/KaTeX_Main-Italic.ttfbin0 -> 47640 bytes
-rw-r--r--public/css/fonts/KaTeX_Main-Italic.woffbin0 -> 26228 bytes
-rw-r--r--public/css/fonts/KaTeX_Main-Italic.woff2bin0 -> 22748 bytes
-rw-r--r--public/css/fonts/KaTeX_Main-Regular.ttfbin0 -> 69520 bytes
-rw-r--r--public/css/fonts/KaTeX_Main-Regular.woffbin0 -> 38112 bytes
-rw-r--r--public/css/fonts/KaTeX_Main-Regular.woff2bin0 -> 32464 bytes
-rw-r--r--public/css/fonts/KaTeX_Math-BoldItalic.ttfbin0 -> 39308 bytes
-rw-r--r--public/css/fonts/KaTeX_Math-BoldItalic.woffbin0 -> 22324 bytes
-rw-r--r--public/css/fonts/KaTeX_Math-BoldItalic.woff2bin0 -> 19720 bytes
-rw-r--r--public/css/fonts/KaTeX_Math-Italic.ttfbin0 -> 40992 bytes
-rw-r--r--public/css/fonts/KaTeX_Math-Italic.woffbin0 -> 22844 bytes
-rw-r--r--public/css/fonts/KaTeX_Math-Italic.woff2bin0 -> 20096 bytes
-rw-r--r--public/css/fonts/KaTeX_SansSerif-Bold.ttfbin0 -> 33688 bytes
-rw-r--r--public/css/fonts/KaTeX_SansSerif-Bold.woffbin0 -> 18516 bytes
-rw-r--r--public/css/fonts/KaTeX_SansSerif-Bold.woff2bin0 -> 15732 bytes
-rw-r--r--public/css/fonts/KaTeX_SansSerif-Italic.ttfbin0 -> 30960 bytes
-rw-r--r--public/css/fonts/KaTeX_SansSerif-Italic.woffbin0 -> 17572 bytes
-rw-r--r--public/css/fonts/KaTeX_SansSerif-Italic.woff2bin0 -> 15024 bytes
-rw-r--r--public/css/fonts/KaTeX_SansSerif-Regular.ttfbin0 -> 29812 bytes
-rw-r--r--public/css/fonts/KaTeX_SansSerif-Regular.woffbin0 -> 16228 bytes
-rw-r--r--public/css/fonts/KaTeX_SansSerif-Regular.woff2bin0 -> 13708 bytes
-rw-r--r--public/css/fonts/KaTeX_Script-Regular.ttfbin0 -> 24620 bytes
-rw-r--r--public/css/fonts/KaTeX_Script-Regular.woffbin0 -> 13428 bytes
-rw-r--r--public/css/fonts/KaTeX_Script-Regular.woff2bin0 -> 12064 bytes
-rw-r--r--public/css/fonts/KaTeX_Size1-Regular.ttfbin0 -> 12916 bytes
-rw-r--r--public/css/fonts/KaTeX_Size1-Regular.woffbin0 -> 6696 bytes
-rw-r--r--public/css/fonts/KaTeX_Size1-Regular.woff2bin0 -> 5592 bytes
-rw-r--r--public/css/fonts/KaTeX_Size2-Regular.ttfbin0 -> 12172 bytes
-rw-r--r--public/css/fonts/KaTeX_Size2-Regular.woffbin0 -> 6436 bytes
-rw-r--r--public/css/fonts/KaTeX_Size2-Regular.woff2bin0 -> 5392 bytes
-rw-r--r--public/css/fonts/KaTeX_Size3-Regular.ttfbin0 -> 8120 bytes
-rw-r--r--public/css/fonts/KaTeX_Size3-Regular.woffbin0 -> 4568 bytes
-rw-r--r--public/css/fonts/KaTeX_Size3-Regular.woff2bin0 -> 3728 bytes
-rw-r--r--public/css/fonts/KaTeX_Size4-Regular.ttfbin0 -> 11016 bytes
-rw-r--r--public/css/fonts/KaTeX_Size4-Regular.woffbin0 -> 6184 bytes
-rw-r--r--public/css/fonts/KaTeX_Size4-Regular.woff2bin0 -> 5028 bytes
-rw-r--r--public/css/fonts/KaTeX_Typewriter-Regular.ttfbin0 -> 35924 bytes
-rw-r--r--public/css/fonts/KaTeX_Typewriter-Regular.woffbin0 -> 20260 bytes
-rw-r--r--public/css/fonts/KaTeX_Typewriter-Regular.woff2bin0 -> 17272 bytes
-rw-r--r--public/css/katex.min.css1052
-rw-r--r--public/css/style.css229
-rw-r--r--public/css/tomorrow.min.css75
-rw-r--r--public/img/a11y-invite.pngbin0 -> 21535 bytes
-rw-r--r--public/img/accessicademy.pngbin0 -> 27178 bytes
-rw-r--r--public/img/ama.pngbin0 -> 368235 bytes
-rw-r--r--public/img/custom-filters.pngbin0 -> 78708 bytes
-rw-r--r--public/img/digits.PNGbin0 -> 84246 bytes
-rw-r--r--public/img/extension-settings.pngbin0 -> 92642 bytes
-rw-r--r--public/img/github-spam.pngbin0 -> 118059 bytes
-rw-r--r--public/img/greasemonkey-confirm.pngbin0 -> 82273 bytes
-rw-r--r--public/img/greasemonkey.pngbin0 -> 100730 bytes
-rw-r--r--public/img/spam.pngbin0 -> 106441 bytes
-rw-r--r--public/img/tota11y.pngbin0 -> 64537 bytes
-rw-r--r--public/img/voiceover.pngbin0 -> 13870 bytes
-rw-r--r--public/img/window.pngbin0 -> 11782 bytes
-rw-r--r--public/js/bowling.js402
-rw-r--r--public/js/color-blindness.js30
-rw-r--r--public/js/demos/clicking.js24
-rw-r--r--public/js/demos/hover.js42
-rw-r--r--public/js/flasher.js16
-rw-r--r--public/js/fraction.min.js18
-rw-r--r--save-32x32.js24
-rw-r--r--save-article.js40
-rw-r--r--save-index.js37
-rw-r--r--save-static-file.js22
-rw-r--r--templates/article.ejs39
-rw-r--r--templates/index.ejs21
-rw-r--r--templates/layout.ejs122
-rw-r--r--yarn.lock2140
140 files changed, 10179 insertions, 0 deletions
diff --git a/.gitignore b/.gitignore
new file mode 100644
index 0000000..e0e6e61
--- /dev/null
+++ b/.gitignore
@@ -0,0 +1,2 @@
+node_modules/
+output/
diff --git a/.jshintrc b/.jshintrc
new file mode 100644
index 0000000..e688987
--- /dev/null
+++ b/.jshintrc
@@ -0,0 +1,3 @@
+{
+ "esversion": 6
+}
diff --git a/.prettierrc b/.prettierrc
new file mode 100644
index 0000000..f6ca428
--- /dev/null
+++ b/.prettierrc
@@ -0,0 +1,6 @@
+{
+ "trailingComma": "es5",
+ "tabWidth": 2,
+ "semi": false,
+ "singleQuote": false
+}
diff --git a/README.md b/README.md
new file mode 100644
index 0000000..e43d410
--- /dev/null
+++ b/README.md
@@ -0,0 +1,20 @@
+## thatjdanisso.cool
+
+This is a collection of node scripts to build my blog - https://thatjdanisso.cool
+
+### building
+
+```
+$ npm install
+$ npm run build
+```
+
+You can also run `npm run watch` to auto-build on relevant file changes.
+
+### editing
+
+I shipped an editor for some reason. You can access it with:
+
+```
+$ npm run editor
+```
diff --git a/articles/2020-11-06-welcome.md b/articles/2020-11-06-welcome.md
new file mode 100644
index 0000000..3a860ad
--- /dev/null
+++ b/articles/2020-11-06-welcome.md
@@ -0,0 +1,10 @@
+---
+title: Welcome
+route: /welcome
+date: 2020-11-06
+description: Welcome, read some, stay for more, struggle along side me.
+---
+
+You're probably wondering what this is, well, quite honestly, I don't really know. I've been told that I tell good stories, by this, I think people actually mean to say <em>"I love to read through your struggles that you put in your source code."</em>... Oh, I couldn't agree more...
+
+Well, if you wan't more of that but minus the <em>"having to sift through your source code"</em> part, stay for more!
diff --git a/articles/programming-languages-code.js b/articles/programming-languages-code.js
new file mode 100644
index 0000000..cb7d764
--- /dev/null
+++ b/articles/programming-languages-code.js
@@ -0,0 +1,180 @@
+/* function evaluate(node) {
+ switch (node.type) {
+ case "HelloWorld":
+ return "Hello, world!";
+ default:
+ throw `evaluate -- unknown node type ${node.type}`;
+ }
+}
+
+const program = { type: "HelloWorld" };
+console.log(evaluate(program));
+// => Hello, world!
+
+console.log(evaluate({ type: "Blah" }));
+
+function evaluate(node) {
+ switch (node.type) {
+ case "String":
+ return node.content;
+ case "Excite":
+ return evaluate(node.expression) + "!";
+ default:
+ throw `evaluate -- unknown node type ${node.type}`;
+ }
+}
+
+console.log(
+ evaluate({
+ type: "String",
+ content: "Apple"
+ })
+);
+// => Apple
+
+console.log(
+ evaluate({
+ type: "Excite",
+ expression: {
+ type: "Excite",
+ expression: {
+ type: "String",
+ content: "Banana"
+ }
+ }
+ })
+);
+// => Banana!!
+
+console.log(
+ evaluate({
+ type: "Excite",
+ expression: {
+ type: "String",
+ content: "Banana"
+ }
+ })
+);
+// => Banana!
+
+console.log(
+ evaluate({
+ type: "Append",
+ first: {
+ type: "String",
+ content: "Apple"
+ },
+ second: {
+ type: "Excite",
+ expression: {
+ type: "String",
+ content: "Banana"
+ }
+ }
+ })
+);
+// => AppleBanana! */
+
+function lookup(env, name) {
+ return env[name];
+}
+
+function extendEnv(env, name, value) {
+ return {
+ ...env,
+ [name]: value,
+ };
+}
+
+function evaluate(node, env) {
+ switch (node.type) {
+ case "String":
+ return node.content;
+ case "Excite":
+ return evaluate(node.expression, env) + "!";
+ case "Append":
+ return evaluate(node.first, env) + evaluate(node.second, env);
+ case "Variable":
+ return lookup(env, node.name);
+ case "Let":
+ let inner = node.expression;
+ let value = evaluate(node.value, env);
+ let newEnv = extendEnv(env, node.name, value);
+
+ return evaluate(node.expression, newEnv);
+ default:
+ throw `evaluate -- unknown node type ${node.type}`;
+ }
+}
+
+console.log(
+ evaluate({
+ type: "String",
+ content: "Apple",
+ })
+);
+// => Apple
+
+console.log(
+ evaluate({
+ type: "Excite",
+ expression: {
+ type: "String",
+ content: "Banana",
+ },
+ })
+);
+// => Banana!
+
+console.log(
+ evaluate({
+ type: "Append",
+ first: {
+ type: "String",
+ content: "Apple",
+ },
+ second: {
+ type: "Excite",
+ expression: {
+ type: "String",
+ content: "Banana",
+ },
+ },
+ })
+);
+// => AppleBanana!
+
+console.log(
+ evaluate(
+ {
+ type: "Let",
+ name: "x",
+ value: {
+ type: "String",
+ content: "Hello, world",
+ },
+ expression: {
+ type: "Excite",
+ expression: {
+ type: "Variable",
+ name: "x",
+ },
+ },
+ },
+ {}
+ )
+);
+// => Hello, world!
+
+console.log(
+ evaluate({
+ type: "Excite",
+ expression: {
+ type: "Excite",
+ expression: {
+ type: "String",
+ content: "Banana",
+ },
+ },
+ })
+);
diff --git a/editor/editor.js b/editor/editor.js
new file mode 100644
index 0000000..63a00d4
--- /dev/null
+++ b/editor/editor.js
@@ -0,0 +1,79 @@
+const express = require("express");
+const ejs = require("ejs");
+const fs = require("fs");
+const glob = require("glob");
+const loadArticle = require("../load-article.js");
+const multer = require("multer");
+const path = require("path");
+
+const app = express();
+
+app.use("/editor", express.static(path.join(__dirname, "public")));
+app.use(express.static(path.join(__dirname, "../public")));
+
+app.get("/", (req, res) => {
+ glob("articles/*.md", (err, articles) => {
+ if (err) { throw err; }
+
+ Promise.all(articles.map(loadArticle)).then(articles => {
+ res.end(
+ ejs.render(
+ fs.readFileSync(
+ path.join(__dirname, "../templates/layout.ejs"),
+ "utf-8"
+ ),
+ {
+ articles: articles,
+ title: "New post | jordan scales",
+ description: "A new post",
+ isEditor: true,
+ body: ejs.render(
+ fs.readFileSync(
+ path.join(__dirname, "../templates/article.ejs"),
+ "utf-8"
+ ),
+ {
+ timeless: false,
+ hidden: false,
+ date: "January 01, 1970",
+ title: "New post",
+ tags: [],
+ body: "<p>Start writing...</p>",
+ tweetUrl: "",
+ }
+ ),
+ }
+ )
+ );
+ });
+ });
+});
+
+const upload = multer();
+app.post("/save", upload.array(), (req, res) => {
+ const fileContents = [
+ "---",
+ `title: ${req.body.title}`,
+ `route: ${req.body.route}`,
+ `date: ${req.body.date}`,
+ `description: ${req.body.description}`,
+ "---",
+ "",
+ req.body.content.replace(/\r\n/g, "\n"),
+ ].join("\n");
+
+ fs.writeFile(
+ path.join(__dirname, "../articles", req.body.filename),
+ fileContents,
+ (err, data) => {
+ if (err) {
+ res.sendStatus(500);
+ res.end("Sorry :(");
+ }
+ res.end("Saved!");
+ }
+ );
+});
+
+console.log("Listening on http://localhost:8080...");
+app.listen(8080);
diff --git a/editor/public/editor.css b/editor/public/editor.css
new file mode 100644
index 0000000..5271e5c
--- /dev/null
+++ b/editor/public/editor.css
@@ -0,0 +1,30 @@
+.main {
+ margin: 0;
+}
+
+#editor {
+ font-size: 16px;
+ border-left: 1px solid black;
+ position: fixed;
+ top: 0; bottom: 0;
+ right: 0;
+ width: 500px;
+ padding: 10px;
+}
+
+#editor input,
+#editor textarea,
+#editor button,
+#editor select {
+ width: 100%;
+ font-family: "Inconsolata", monospace;
+ font-size: 16px;
+}
+
+#editor textarea {
+ height: 418px;
+}
+
+#editor-save-button {
+ height: 40px;
+}
diff --git a/editor/public/editor.js b/editor/public/editor.js
new file mode 100644
index 0000000..902b566
--- /dev/null
+++ b/editor/public/editor.js
@@ -0,0 +1,87 @@
+const $editor = document.getElementById("editor")
+const $title = document.getElementById("editor-title")
+const $date = document.getElementById("editor-date")
+const $content = document.getElementById("editor-content")
+const $loader = document.getElementById("editor-loader")
+const $save = document.getElementById("editor-save-button")
+
+const $outputTitle = document.querySelector(".title h1")
+const $outputDate = document.querySelector(".date")
+const $outputContent = document.querySelector(".content")
+
+$loader.addEventListener("change", e => {
+ if (e.target.value) {
+ const article = JSON.parse(e.target.value)
+
+ $title.value = article.title
+ document.title = article.title + " | jordan scales"
+ $outputTitle.innerHTML = article.title
+
+ $date.value = strftime("%F", new Date(article.rawDate))
+ $outputDate.innerHTML = article.date
+
+ $content.value = article.rawBody
+ $outputContent.innerHTML = article.body
+
+ document.getElementById("editor-description").value = article.description
+
+ // Hax!!! Get rid of the "articles/" prefix
+ document.getElementById("editor-filename").value = article.filename.replace(
+ /^articles\//,
+ ""
+ )
+ document.getElementById("editor-route").value = article.route
+ }
+})
+
+$title.addEventListener("keyup", e => {
+ document.title = e.target.value + " | jordan scales"
+ $outputTitle.innerHTML = e.target.value
+})
+
+$content.addEventListener("keyup", e => {
+ $outputContent.innerHTML = marked(e.target.value)
+
+ renderMathInElement(document.body, {
+ displayMode: true,
+ })
+})
+
+$content.addEventListener("keydown", e => {
+ if (9 === e.keyCode) {
+ e.preventDefault()
+ const textarea = e.target
+ const pos = textarea.selectionStart
+ textarea.value =
+ textarea.value.slice(0, pos) + " " + textarea.value.slice(pos)
+ }
+})
+
+$date.addEventListener("keyup", e => {
+ const rawDate = new Date(e.target.value)
+ ;["FullYear", "Month", "Date", "Hours"].forEach(field => {
+ rawDate["set" + field](rawDate["getUTC" + field]())
+ })
+
+ $outputDate.innerHTML = strftime("%B %d, %Y", rawDate)
+})
+
+$editor.addEventListener("submit", e => {
+ e.preventDefault()
+ $save.innerHTML = "Saving..."
+
+ fetch("/save", {
+ method: "POST",
+ body: new FormData($editor),
+ }).then(res => {
+ if (res.status === 200) {
+ $save.innerHTML = "Saved!"
+ } else {
+ $save.innerHTML = "Error :( Try again"
+ }
+
+ setTimeout(() => {
+ $save.innerHTML = "Save"
+ }, 1500)
+ })
+})
diff --git a/editor/public/ejs.min.js b/editor/public/ejs.min.js
new file mode 100644
index 0000000..880763d
--- /dev/null
+++ b/editor/public/ejs.min.js
@@ -0,0 +1 @@
+(function(f){if(typeof exports==="object"&&typeof module!=="undefined"){module.exports=f()}else if(typeof define==="function"&&define.amd){define([],f)}else{var g;if(typeof window!=="undefined"){g=window}else if(typeof global!=="undefined"){g=global}else if(typeof self!=="undefined"){g=self}else{g=this}g.ejs=f()}})(function(){var define,module,exports;return function e(t,n,r){function s(o,u){if(!n[o]){if(!t[o]){var a=typeof require=="function"&&require;if(!u&&a)return a(o,!0);if(i)return i(o,!0);var f=new Error("Cannot find module '"+o+"'");throw f.code="MODULE_NOT_FOUND",f}var l=n[o]={exports:{}};t[o][0].call(l.exports,function(e){var n=t[o][1][e];return s(n?n:e)},l,l.exports,e,t,n,r)}return n[o].exports}var i=typeof require=="function"&&require;for(var o=0;o<r.length;o++)s(r[o]);return s}({1:[function(require,module,exports){"use strict";var fs=require("fs");var path=require("path");var utils=require("./utils");var scopeOptionWarned=false;var _VERSION_STRING=require("../package.json").version;var _DEFAULT_DELIMITER="%";var _DEFAULT_LOCALS_NAME="locals";var _NAME="ejs";var _REGEX_STRING="(<%%|%%>|<%=|<%-|<%_|<%#|<%|%>|-%>|_%>)";var _OPTS=["delimiter","scope","context","debug","compileDebug","client","_with","rmWhitespace","strict","filename"];var _OPTS_EXPRESS=_OPTS.concat("cache");var _BOM=/^\uFEFF/;exports.cache=utils.cache;exports.fileLoader=fs.readFileSync;exports.localsName=_DEFAULT_LOCALS_NAME;exports.resolveInclude=function(name,filename,isDir){var dirname=path.dirname;var extname=path.extname;var resolve=path.resolve;var includePath=resolve(isDir?filename:dirname(filename),name);var ext=extname(name);if(!ext){includePath+=".ejs"}return includePath};function getIncludePath(path,options){var includePath;var filePath;var views=options.views;if(path.charAt(0)=="/"){includePath=exports.resolveInclude(path.replace(/^\/*/,""),options.root||"/",true)}else{if(options.filename){filePath=exports.resolveInclude(path,options.filename);if(fs.existsSync(filePath)){includePath=filePath}}if(!includePath){if(Array.isArray(views)&&views.some(function(v){filePath=exports.resolveInclude(path,v,true);return fs.existsSync(filePath)})){includePath=filePath}}if(!includePath){throw new Error("Could not find include include file.")}}return includePath}function handleCache(options,template){var func;var filename=options.filename;var hasTemplate=arguments.length>1;if(options.cache){if(!filename){throw new Error("cache option requires a filename")}func=exports.cache.get(filename);if(func){return func}if(!hasTemplate){template=fileLoader(filename).toString().replace(_BOM,"")}}else if(!hasTemplate){if(!filename){throw new Error("Internal EJS error: no file name or template "+"provided")}template=fileLoader(filename).toString().replace(_BOM,"")}func=exports.compile(template,options);if(options.cache){exports.cache.set(filename,func)}return func}function tryHandleCache(options,data,cb){var result;try{result=handleCache(options)(data)}catch(err){return cb(err)}return cb(null,result)}function fileLoader(filePath){return exports.fileLoader(filePath)}function includeFile(path,options){var opts=utils.shallowCopy({},options);opts.filename=getIncludePath(path,opts);return handleCache(opts)}function includeSource(path,options){var opts=utils.shallowCopy({},options);var includePath;var template;includePath=getIncludePath(path,opts);template=fileLoader(includePath).toString().replace(_BOM,"");opts.filename=includePath;var templ=new Template(template,opts);templ.generateSource();return{source:templ.source,filename:includePath,template:template}}function rethrow(err,str,flnm,lineno,esc){var lines=str.split("\n");var start=Math.max(lineno-3,0);var end=Math.min(lines.length,lineno+3);var filename=esc(flnm);var context=lines.slice(start,end).map(function(line,i){var curr=i+start+1;return(curr==lineno?" >> ":" ")+curr+"| "+line}).join("\n");err.path=filename;err.message=(filename||"ejs")+":"+lineno+"\n"+context+"\n\n"+err.message;throw err}function stripSemi(str){return str.replace(/;(\s*$)/,"$1")}exports.compile=function compile(template,opts){var templ;if(opts&&opts.scope){if(!scopeOptionWarned){console.warn("`scope` option is deprecated and will be removed in EJS 3");scopeOptionWarned=true}if(!opts.context){opts.context=opts.scope}delete opts.scope}templ=new Template(template,opts);return templ.compile()};exports.render=function(template,d,o){var data=d||{};var opts=o||{};if(arguments.length==2){utils.shallowCopyFromList(opts,data,_OPTS)}return handleCache(opts,template)(data)};exports.renderFile=function(){var filename=arguments[0];var cb=arguments[arguments.length-1];var opts={filename:filename};var data;if(arguments.length>2){data=arguments[1];if(arguments.length===3){if(data.settings){if(data.settings["view options"]){utils.shallowCopyFromList(opts,data.settings["view options"],_OPTS_EXPRESS)}if(data.settings.views){opts.views=data.settings.views}}else{utils.shallowCopyFromList(opts,data,_OPTS_EXPRESS)}}else{utils.shallowCopy(opts,arguments[2])}opts.filename=filename}else{data={}}return tryHandleCache(opts,data,cb)};exports.clearCache=function(){exports.cache.reset()};function Template(text,opts){opts=opts||{};var options={};this.templateText=text;this.mode=null;this.truncate=false;this.currentLine=1;this.source="";this.dependencies=[];options.client=opts.client||false;options.escapeFunction=opts.escape||utils.escapeXML;options.compileDebug=opts.compileDebug!==false;options.debug=!!opts.debug;options.filename=opts.filename;options.delimiter=opts.delimiter||exports.delimiter||_DEFAULT_DELIMITER;options.strict=opts.strict||false;options.context=opts.context;options.cache=opts.cache||false;options.rmWhitespace=opts.rmWhitespace;options.root=opts.root;options.localsName=opts.localsName||exports.localsName||_DEFAULT_LOCALS_NAME;options.views=opts.views;if(options.strict){options._with=false}else{options._with=typeof opts._with!="undefined"?opts._with:true}this.opts=options;this.regex=this.createRegex()}Template.modes={EVAL:"eval",ESCAPED:"escaped",RAW:"raw",COMMENT:"comment",LITERAL:"literal"};Template.prototype={createRegex:function(){var str=_REGEX_STRING;var delim=utils.escapeRegExpChars(this.opts.delimiter);str=str.replace(/%/g,delim);return new RegExp(str)},compile:function(){var src;var fn;var opts=this.opts;var prepended="";var appended="";var escapeFn=opts.escapeFunction;if(!this.source){this.generateSource();prepended+=" var __output = [], __append = __output.push.bind(__output);"+"\n";if(opts._with!==false){prepended+=" with ("+opts.localsName+" || {}) {"+"\n";appended+=" }"+"\n"}appended+=' return __output.join("");'+"\n";this.source=prepended+this.source+appended}if(opts.compileDebug){src="var __line = 1"+"\n"+" , __lines = "+JSON.stringify(this.templateText)+"\n"+" , __filename = "+(opts.filename?JSON.stringify(opts.filename):"undefined")+";"+"\n"+"try {"+"\n"+this.source+"} catch (e) {"+"\n"+" rethrow(e, __lines, __filename, __line, escapeFn);"+"\n"+"}"+"\n"}else{src=this.source}if(opts.client){src="escapeFn = escapeFn || "+escapeFn.toString()+";"+"\n"+src;if(opts.compileDebug){src="rethrow = rethrow || "+rethrow.toString()+";"+"\n"+src}}if(opts.strict){src='"use strict";\n'+src}if(opts.debug){console.log(src)}try{fn=new Function(opts.localsName+", escapeFn, include, rethrow",src)}catch(e){if(e instanceof SyntaxError){if(opts.filename){e.message+=" in "+opts.filename}e.message+=" while compiling ejs\n\n";e.message+="If the above error is not helpful, you may want to try EJS-Lint:\n";e.message+="https://github.com/RyanZim/EJS-Lint"}throw e}if(opts.client){fn.dependencies=this.dependencies;return fn}var returnedFn=function(data){var include=function(path,includeData){var d=utils.shallowCopy({},data);if(includeData){d=utils.shallowCopy(d,includeData)}return includeFile(path,opts)(d)};return fn.apply(opts.context,[data||{},escapeFn,include,rethrow])};returnedFn.dependencies=this.dependencies;return returnedFn},generateSource:function(){var opts=this.opts;if(opts.rmWhitespace){this.templateText=this.templateText.replace(/\r/g,"").replace(/^\s+|\s+$/gm,"")}this.templateText=this.templateText.replace(/[ \t]*<%_/gm,"<%_").replace(/_%>[ \t]*/gm,"_%>");var self=this;var matches=this.parseTemplateText();var d=this.opts.delimiter;if(matches&&matches.length){matches.forEach(function(line,index){var opening;var closing;var include;var includeOpts;var includeObj;var includeSrc;if(line.indexOf("<"+d)===0&&line.indexOf("<"+d+d)!==0){closing=matches[index+2];if(!(closing==d+">"||closing=="-"+d+">"||closing=="_"+d+">")){throw new Error('Could not find matching close tag for "'+line+'".')}}if(include=line.match(/^\s*include\s+(\S+)/)){opening=matches[index-1];if(opening&&(opening=="<"+d||opening=="<"+d+"-"||opening=="<"+d+"_")){includeOpts=utils.shallowCopy({},self.opts);includeObj=includeSource(include[1],includeOpts);if(self.opts.compileDebug){includeSrc=" ; (function(){"+"\n"+" var __line = 1"+"\n"+" , __lines = "+JSON.stringify(includeObj.template)+"\n"+" , __filename = "+JSON.stringify(includeObj.filename)+";"+"\n"+" try {"+"\n"+includeObj.source+" } catch (e) {"+"\n"+" rethrow(e, __lines, __filename, __line, escapeFn);"+"\n"+" }"+"\n"+" ; }).call(this)"+"\n"}else{includeSrc=" ; (function(){"+"\n"+includeObj.source+" ; }).call(this)"+"\n"}self.source+=includeSrc;self.dependencies.push(exports.resolveInclude(include[1],includeOpts.filename));return}}self.scanLine(line)})}},parseTemplateText:function(){var str=this.templateText;var pat=this.regex;var result=pat.exec(str);var arr=[];var firstPos;while(result){firstPos=result.index;if(firstPos!==0){arr.push(str.substring(0,firstPos));str=str.slice(firstPos)}arr.push(result[0]);str=str.slice(result[0].length);result=pat.exec(str)}if(str){arr.push(str)}return arr},_addOutput:function(line){if(this.truncate){line=line.replace(/^(?:\r\n|\r|\n)/,"");this.truncate=false}else if(this.opts.rmWhitespace){line=line.replace(/^\n/,"")}if(!line){return line}line=line.replace(/\\/g,"\\\\");line=line.replace(/\n/g,"\\n");line=line.replace(/\r/g,"\\r");line=line.replace(/"/g,'\\"');this.source+=' ; __append("'+line+'")'+"\n"},scanLine:function(line){var self=this;var d=this.opts.delimiter;var newLineCount=0;newLineCount=line.split("\n").length-1;switch(line){case"<"+d:case"<"+d+"_":this.mode=Template.modes.EVAL;break;case"<"+d+"=":this.mode=Template.modes.ESCAPED;break;case"<"+d+"-":this.mode=Template.modes.RAW;break;case"<"+d+"#":this.mode=Template.modes.COMMENT;break;case"<"+d+d:this.mode=Template.modes.LITERAL;this.source+=' ; __append("'+line.replace("<"+d+d,"<"+d)+'")'+"\n";break;case d+d+">":this.mode=Template.modes.LITERAL;this.source+=' ; __append("'+line.replace(d+d+">",d+">")+'")'+"\n";break;case d+">":case"-"+d+">":case"_"+d+">":if(this.mode==Template.modes.LITERAL){this._addOutput(line)}this.mode=null;this.truncate=line.indexOf("-")===0||line.indexOf("_")===0;break;default:if(this.mode){switch(this.mode){case Template.modes.EVAL:case Template.modes.ESCAPED:case Template.modes.RAW:if(line.lastIndexOf("//")>line.lastIndexOf("\n")){line+="\n"}}switch(this.mode){case Template.modes.EVAL:this.source+=" ; "+line+"\n";break;case Template.modes.ESCAPED:this.source+=" ; __append(escapeFn("+stripSemi(line)+"))"+"\n";break;case Template.modes.RAW:this.source+=" ; __append("+stripSemi(line)+")"+"\n";break;case Template.modes.COMMENT:break;case Template.modes.LITERAL:this._addOutput(line);break}}else{this._addOutput(line)}}if(self.opts.compileDebug&&newLineCount){this.currentLine+=newLineCount;this.source+=" ; __line = "+this.currentLine+"\n"}}};exports.escapeXML=utils.escapeXML;exports.__express=exports.renderFile;if(require.extensions){require.extensions[".ejs"]=function(module,flnm){var filename=flnm||module.filename;var options={filename:filename,client:true};var template=fileLoader(filename).toString();var fn=exports.compile(template,options);module._compile("module.exports = "+fn.toString()+";",filename)}}exports.VERSION=_VERSION_STRING;exports.name=_NAME;if(typeof window!="undefined"){window.ejs=exports}},{"../package.json":6,"./utils":2,fs:3,path:4}],2:[function(require,module,exports){"use strict";var regExpChars=/[|\\{}()[\]^$+*?.]/g;exports.escapeRegExpChars=function(string){if(!string){return""}return String(string).replace(regExpChars,"\\$&")};var _ENCODE_HTML_RULES={"&":"&amp;","<":"&lt;",">":"&gt;",'"':"&#34;","'":"&#39;"};var _MATCH_HTML=/[&<>\'"]/g;function encode_char(c){return _ENCODE_HTML_RULES[c]||c}var escapeFuncStr="var _ENCODE_HTML_RULES = {\n"+' "&": "&amp;"\n'+' , "<": "&lt;"\n'+' , ">": "&gt;"\n'+' , \'"\': "&#34;"\n'+' , "\'": "&#39;"\n'+" }\n"+" , _MATCH_HTML = /[&<>'\"]/g;\n"+"function encode_char(c) {\n"+" return _ENCODE_HTML_RULES[c] || c;\n"+"};\n";exports.escapeXML=function(markup){return markup==undefined?"":String(markup).replace(_MATCH_HTML,encode_char)};exports.escapeXML.toString=function(){return Function.prototype.toString.call(this)+";\n"+escapeFuncStr};exports.shallowCopy=function(to,from){from=from||{};for(var p in from){to[p]=from[p]}return to};exports.shallowCopyFromList=function(to,from,list){for(var i=0;i<list.length;i++){var p=list[i];if(typeof from[p]!="undefined"){to[p]=from[p]}}return to};exports.cache={_data:{},set:function(key,val){this._data[key]=val},get:function(key){return this._data[key]},reset:function(){this._data={}}}},{}],3:[function(require,module,exports){},{}],4:[function(require,module,exports){(function(process){function normalizeArray(parts,allowAboveRoot){var up=0;for(var i=parts.length-1;i>=0;i--){var last=parts[i];if(last==="."){parts.splice(i,1)}else if(last===".."){parts.splice(i,1);up++}else if(up){parts.splice(i,1);up--}}if(allowAboveRoot){for(;up--;up){parts.unshift("..")}}return parts}var splitPathRe=/^(\/?|)([\s\S]*?)((?:\.{1,2}|[^\/]+?|)(\.[^.\/]*|))(?:[\/]*)$/;var splitPath=function(filename){return splitPathRe.exec(filename).slice(1)};exports.resolve=function(){var resolvedPath="",resolvedAbsolute=false;for(var i=arguments.length-1;i>=-1&&!resolvedAbsolute;i--){var path=i>=0?arguments[i]:process.cwd();if(typeof path!=="string"){throw new TypeError("Arguments to path.resolve must be strings")}else if(!path){continue}resolvedPath=path+"/"+resolvedPath;resolvedAbsolute=path.charAt(0)==="/"}resolvedPath=normalizeArray(filter(resolvedPath.split("/"),function(p){return!!p}),!resolvedAbsolute).join("/");return(resolvedAbsolute?"/":"")+resolvedPath||"."};exports.normalize=function(path){var isAbsolute=exports.isAbsolute(path),trailingSlash=substr(path,-1)==="/";path=normalizeArray(filter(path.split("/"),function(p){return!!p}),!isAbsolute).join("/");if(!path&&!isAbsolute){path="."}if(path&&trailingSlash){path+="/"}return(isAbsolute?"/":"")+path};exports.isAbsolute=function(path){return path.charAt(0)==="/"};exports.join=function(){var paths=Array.prototype.slice.call(arguments,0);return exports.normalize(filter(paths,function(p,index){if(typeof p!=="string"){throw new TypeError("Arguments to path.join must be strings")}return p}).join("/"))};exports.relative=function(from,to){from=exports.resolve(from).substr(1);to=exports.resolve(to).substr(1);function trim(arr){var start=0;for(;start<arr.length;start++){if(arr[start]!=="")break}var end=arr.length-1;for(;end>=0;end--){if(arr[end]!=="")break}if(start>end)return[];return arr.slice(start,end-start+1)}var fromParts=trim(from.split("/"));var toParts=trim(to.split("/"));var length=Math.min(fromParts.length,toParts.length);var samePartsLength=length;for(var i=0;i<length;i++){if(fromParts[i]!==toParts[i]){samePartsLength=i;break}}var outputParts=[];for(var i=samePartsLength;i<fromParts.length;i++){outputParts.push("..")}outputParts=outputParts.concat(toParts.slice(samePartsLength));return outputParts.join("/")};exports.sep="/";exports.delimiter=":";exports.dirname=function(path){var result=splitPath(path),root=result[0],dir=result[1];if(!root&&!dir){return"."}if(dir){dir=dir.substr(0,dir.length-1)}return root+dir};exports.basename=function(path,ext){var f=splitPath(path)[2];if(ext&&f.substr(-1*ext.length)===ext){f=f.substr(0,f.length-ext.length)}return f};exports.extname=function(path){return splitPath(path)[3]};function filter(xs,f){if(xs.filter)return xs.filter(f);var res=[];for(var i=0;i<xs.length;i++){if(f(xs[i],i,xs))res.push(xs[i])}return res}var substr="ab".substr(-1)==="b"?function(str,start,len){return str.substr(start,len)}:function(str,start,len){if(start<0)start=str.length+start;return str.substr(start,len)}}).call(this,require("_process"))},{_process:5}],5:[function(require,module,exports){var process=module.exports={};var cachedSetTimeout;var cachedClearTimeout;function defaultSetTimout(){throw new Error("setTimeout has not been defined")}function defaultClearTimeout(){throw new Error("clearTimeout has not been defined")}(function(){try{if(typeof setTimeout==="function"){cachedSetTimeout=setTimeout}else{cachedSetTimeout=defaultSetTimout}}catch(e){cachedSetTimeout=defaultSetTimout}try{if(typeof clearTimeout==="function"){cachedClearTimeout=clearTimeout}else{cachedClearTimeout=defaultClearTimeout}}catch(e){cachedClearTimeout=defaultClearTimeout}})();function runTimeout(fun){if(cachedSetTimeout===setTimeout){return setTimeout(fun,0)}if((cachedSetTimeout===defaultSetTimout||!cachedSetTimeout)&&setTimeout){cachedSetTimeout=setTimeout;return setTimeout(fun,0)}try{return cachedSetTimeout(fun,0)}catch(e){try{return cachedSetTimeout.call(null,fun,0)}catch(e){return cachedSetTimeout.call(this,fun,0)}}}function runClearTimeout(marker){if(cachedClearTimeout===clearTimeout){return clearTimeout(marker)}if((cachedClearTimeout===defaultClearTimeout||!cachedClearTimeout)&&clearTimeout){cachedClearTimeout=clearTimeout;return clearTimeout(marker)}try{return cachedClearTimeout(marker)}catch(e){try{return cachedClearTimeout.call(null,marker)}catch(e){return cachedClearTimeout.call(this,marker)}}}var queue=[];var draining=false;var currentQueue;var queueIndex=-1;function cleanUpNextTick(){if(!draining||!currentQueue){return}draining=false;if(currentQueue.length){queue=currentQueue.concat(queue)}else{queueIndex=-1}if(queue.length){drainQueue()}}function drainQueue(){if(draining){return}var timeout=runTimeout(cleanUpNextTick);draining=true;var len=queue.length;while(len){currentQueue=queue;queue=[];while(++queueIndex<len){if(currentQueue){currentQueue[queueIndex].run()}}queueIndex=-1;len=queue.length}currentQueue=null;draining=false;runClearTimeout(timeout)}process.nextTick=function(fun){var args=new Array(arguments.length-1);if(arguments.length>1){for(var i=1;i<arguments.length;i++){args[i-1]=arguments[i]}}queue.push(new Item(fun,args));if(queue.length===1&&!draining){runTimeout(drainQueue)}};function Item(fun,array){this.fun=fun;this.array=array}Item.prototype.run=function(){this.fun.apply(null,this.array)};process.title="browser";process.browser=true;process.env={};process.argv=[];process.version="";process.versions={};function noop(){}process.on=noop;process.addListener=noop;process.once=noop;process.off=noop;process.removeListener=noop;process.removeAllListeners=noop;process.emit=noop;process.binding=function(name){throw new Error("process.binding is not supported")};process.cwd=function(){return"/"};process.chdir=function(dir){throw new Error("process.chdir is not supported")};process.umask=function(){return 0}},{}],6:[function(require,module,exports){module.exports={name:"ejs",description:"Embedded JavaScript templates",keywords:["template","engine","ejs"],version:"2.5.7",author:"Matthew Eernisse <[email protected]> (http://fleegix.org)",contributors:["Timothy Gu <[email protected]> (https://timothygu.github.io)"],license:"Apache-2.0",main:"./lib/ejs.js",repository:{type:"git",url:"git://github.com/mde/ejs.git"},bugs:"https://github.com/mde/ejs/issues",homepage:"https://github.com/mde/ejs",dependencies:{},devDependencies:{browserify:"^13.0.1",eslint:"^3.0.0","git-directory-deploy":"^1.5.1",istanbul:"~0.4.3",jake:"^8.0.0",jsdoc:"^3.4.0","lru-cache":"^4.0.1",mocha:"^3.0.2","uglify-js":"^2.6.2"},engines:{node:">=0.10.0"},scripts:{test:"jake test",lint:'eslint "**/*.js" Jakefile',coverage:"istanbul cover node_modules/mocha/bin/_mocha",doc:"jake doc",devdoc:"jake doc[dev]"}}},{}]},{},[1])(1)});
diff --git a/editor/public/marked.min.js b/editor/public/marked.min.js
new file mode 100644
index 0000000..e0c8dd0
--- /dev/null
+++ b/editor/public/marked.min.js
@@ -0,0 +1,6 @@
+/**
+ * marked - a markdown parser
+ * Copyright (c) 2011-2014, Christopher Jeffrey. (MIT Licensed)
+ * https://github.com/chjj/marked
+ */
+(function(){function e(e){this.tokens=[],this.tokens.links={},this.options=e||p.defaults,this.rules=u.normal,this.options.gfm&&(this.options.tables?this.rules=u.tables:this.rules=u.gfm)}function t(e,t){if(this.options=t||p.defaults,this.links=e,this.rules=c.normal,this.renderer=this.options.renderer||new n,this.renderer.options=this.options,!this.links)throw new Error("Tokens array requires a `links` property.");this.options.gfm?this.options.breaks?this.rules=c.breaks:this.rules=c.gfm:this.options.pedantic&&(this.rules=c.pedantic)}function n(e){this.options=e||{}}function r(e){this.tokens=[],this.token=null,this.options=e||p.defaults,this.options.renderer=this.options.renderer||new n,this.renderer=this.options.renderer,this.renderer.options=this.options}function s(e,t){return e.replace(t?/&/g:/&(?!#?\w+;)/g,"&amp;").replace(/</g,"&lt;").replace(/>/g,"&gt;").replace(/"/g,"&quot;").replace(/'/g,"&#39;")}function i(e){return e.replace(/&(#(?:\d+)|(?:#x[0-9A-Fa-f]+)|(?:\w+));?/gi,function(e,t){return t=t.toLowerCase(),"colon"===t?":":"#"===t.charAt(0)?"x"===t.charAt(1)?String.fromCharCode(parseInt(t.substring(2),16)):String.fromCharCode(+t.substring(1)):""})}function l(e,t){return e=e.source,t=t||"",function n(r,s){return r?(s=s.source||s,s=s.replace(/(^|[^\[])\^/g,"$1"),e=e.replace(r,s),n):new RegExp(e,t)}}function o(e,t){return baseUrls[" "+e]||(/^[^:]+:\/*[^\/]*$/.test(e)?baseUrls[" "+e]=e+"/":baseUrls[" "+e]=e.replace(/[^\/]*$/,"")),e=baseUrls[" "+e],"//"===t.slice(0,2)?e.replace(/:[^]*/,":")+t:"/"===t.charAt(0)?e.replace(/(:\/*[^\/]*)[^]*/,"$1")+t:e+t}function h(){}function a(e){for(var t,n,r=1;r<arguments.length;r++){t=arguments[r];for(n in t)Object.prototype.hasOwnProperty.call(t,n)&&(e[n]=t[n])}return e}function p(t,n,i){if(i||"function"==typeof n){i||(i=n,n=null),n=a({},p.defaults,n||{});var l,o,h=n.highlight,u=0;try{l=e.lex(t,n)}catch(c){return i(c)}o=l.length;var g=function(e){if(e)return n.highlight=h,i(e);var t;try{t=r.parse(l,n)}catch(s){e=s}return n.highlight=h,e?i(e):i(null,t)};if(!h||h.length<3)return g();if(delete n.highlight,!o)return g();for(;u<l.length;u++)!function(e){return"code"!==e.type?--o||g():h(e.text,e.lang,function(t,n){return t?g(t):null==n||n===e.text?--o||g():(e.text=n,e.escaped=!0,void(--o||g()))})}(l[u])}else try{return n&&(n=a({},p.defaults,n)),r.parse(e.lex(t,n),n)}catch(c){if(c.message+="\nPlease report this to https://github.com/chjj/marked.",(n||p.defaults).silent)return"<p>An error occured:</p><pre>"+s(c.message+"",!0)+"</pre>";throw c}}var u={newline:/^\n+/,code:/^( {4}[^\n]+\n*)+/,fences:h,hr:/^( *[-*_]){3,} *(?:\n+|$)/,heading:/^ *(#{1,6}) *([^\n]+?) *#* *(?:\n+|$)/,nptable:h,lheading:/^([^\n]+)\n *(=|-){2,} *(?:\n+|$)/,blockquote:/^( *>[^\n]+(\n(?!def)[^\n]+)*\n*)+/,list:/^( *)(bull) [\s\S]+?(?:hr|def|\n{2,}(?! )(?!\1bull )\n*|\s*$)/,html:/^ *(?:comment *(?:\n|\s*$)|closed *(?:\n{2,}|\s*$)|closing *(?:\n{2,}|\s*$))/,def:/^ *\[([^\]]+)\]: *<?([^\s>]+)>?(?: +["(]([^\n]+)[")])? *(?:\n+|$)/,table:h,paragraph:/^((?:[^\n]+\n?(?!hr|heading|lheading|blockquote|tag|def))+)\n*/,text:/^[^\n]+/};u.bullet=/(?:[*+-]|\d+\.)/,u.item=/^( *)(bull) [^\n]*(?:\n(?!\1bull )[^\n]*)*/,u.item=l(u.item,"gm")(/bull/g,u.bullet)(),u.list=l(u.list)(/bull/g,u.bullet)("hr","\\n+(?=\\1?(?:[-*_] *){3,}(?:\\n+|$))")("def","\\n+(?="+u.def.source+")")(),u._tag="(?!(?:a|em|strong|small|s|cite|q|dfn|abbr|data|time|code|var|samp|kbd|sub|sup|i|b|u|mark|ruby|rt|rp|bdi|bdo|span|br|wbr|ins|del|img)\\b)\\w+(?!:/|[^\\w\\s@]*@)\\b",u.html=l(u.html)("comment",/<!--[\s\S]*?-->/)("closed",/<(tag)[\s\S]+?<\/\1>/)("closing",/<tag(?:"[^"]*"|'[^']*'|[^'">])*?>/)(/tag/g,u._tag)(),u.paragraph=l(u.paragraph)("hr",u.hr)("heading",u.heading)("lheading",u.lheading)("blockquote",u.blockquote)("tag","<"+u._tag)("def",u.def)(),u.normal=a({},u),u.gfm=a({},u.normal,{fences:/^ *(`{3,}|~{3,})[ \.]*(\S+)? *\n([\s\S]*?)\s*\1 *(?:\n+|$)/,paragraph:/^/,heading:/^ *(#{1,6}) +([^\n]+?) *#* *(?:\n+|$)/}),u.gfm.paragraph=l(u.paragraph)("(?!","(?!"+u.gfm.fences.source.replace("\\1","\\2")+"|"+u.list.source.replace("\\1","\\3")+"|")(),u.tables=a({},u.gfm,{nptable:/^ *(\S.*\|.*)\n *([-:]+ *\|[-| :]*)\n((?:.*\|.*(?:\n|$))*)\n*/,table:/^ *\|(.+)\n *\|( *[-:]+[-| :]*)\n((?: *\|.*(?:\n|$))*)\n*/}),e.rules=u,e.lex=function(t,n){var r=new e(n);return r.lex(t)},e.prototype.lex=function(e){return e=e.replace(/\r\n|\r/g,"\n").replace(/\t/g," ").replace(/\u00a0/g," ").replace(/\u2424/g,"\n"),this.token(e,!0)},e.prototype.token=function(e,t,n){for(var r,s,i,l,o,h,a,p,c,e=e.replace(/^ +$/gm,"");e;)if((i=this.rules.newline.exec(e))&&(e=e.substring(i[0].length),i[0].length>1&&this.tokens.push({type:"space"})),i=this.rules.code.exec(e))e=e.substring(i[0].length),i=i[0].replace(/^ {4}/gm,""),this.tokens.push({type:"code",text:this.options.pedantic?i:i.replace(/\n+$/,"")});else if(i=this.rules.fences.exec(e))e=e.substring(i[0].length),this.tokens.push({type:"code",lang:i[2],text:i[3]||""});else if(i=this.rules.heading.exec(e))e=e.substring(i[0].length),this.tokens.push({type:"heading",depth:i[1].length,text:i[2]});else if(t&&(i=this.rules.nptable.exec(e))){for(e=e.substring(i[0].length),h={type:"table",header:i[1].replace(/^ *| *\| *$/g,"").split(/ *\| */),align:i[2].replace(/^ *|\| *$/g,"").split(/ *\| */),cells:i[3].replace(/\n$/,"").split("\n")},p=0;p<h.align.length;p++)/^ *-+: *$/.test(h.align[p])?h.align[p]="right":/^ *:-+: *$/.test(h.align[p])?h.align[p]="center":/^ *:-+ *$/.test(h.align[p])?h.align[p]="left":h.align[p]=null;for(p=0;p<h.cells.length;p++)h.cells[p]=h.cells[p].split(/ *\| */);this.tokens.push(h)}else if(i=this.rules.lheading.exec(e))e=e.substring(i[0].length),this.tokens.push({type:"heading",depth:"="===i[2]?1:2,text:i[1]});else if(i=this.rules.hr.exec(e))e=e.substring(i[0].length),this.tokens.push({type:"hr"});else if(i=this.rules.blockquote.exec(e))e=e.substring(i[0].length),this.tokens.push({type:"blockquote_start"}),i=i[0].replace(/^ *> ?/gm,""),this.token(i,t,!0),this.tokens.push({type:"blockquote_end"});else if(i=this.rules.list.exec(e)){for(e=e.substring(i[0].length),l=i[2],this.tokens.push({type:"list_start",ordered:l.length>1}),i=i[0].match(this.rules.item),r=!1,c=i.length,p=0;p<c;p++)h=i[p],a=h.length,h=h.replace(/^ *([*+-]|\d+\.) +/,""),~h.indexOf("\n ")&&(a-=h.length,h=this.options.pedantic?h.replace(/^ {1,4}/gm,""):h.replace(new RegExp("^ {1,"+a+"}","gm"),"")),this.options.smartLists&&p!==c-1&&(o=u.bullet.exec(i[p+1])[0],l===o||l.length>1&&o.length>1||(e=i.slice(p+1).join("\n")+e,p=c-1)),s=r||/\n\n(?!\s*$)/.test(h),p!==c-1&&(r="\n"===h.charAt(h.length-1),s||(s=r)),this.tokens.push({type:s?"loose_item_start":"list_item_start"}),this.token(h,!1,n),this.tokens.push({type:"list_item_end"});this.tokens.push({type:"list_end"})}else if(i=this.rules.html.exec(e))e=e.substring(i[0].length),this.tokens.push({type:this.options.sanitize?"paragraph":"html",pre:!this.options.sanitizer&&("pre"===i[1]||"script"===i[1]||"style"===i[1]),text:i[0]});else if(!n&&t&&(i=this.rules.def.exec(e)))e=e.substring(i[0].length),this.tokens.links[i[1].toLowerCase()]={href:i[2],title:i[3]};else if(t&&(i=this.rules.table.exec(e))){for(e=e.substring(i[0].length),h={type:"table",header:i[1].replace(/^ *| *\| *$/g,"").split(/ *\| */),align:i[2].replace(/^ *|\| *$/g,"").split(/ *\| */),cells:i[3].replace(/(?: *\| *)?\n$/,"").split("\n")},p=0;p<h.align.length;p++)/^ *-+: *$/.test(h.align[p])?h.align[p]="right":/^ *:-+: *$/.test(h.align[p])?h.align[p]="center":/^ *:-+ *$/.test(h.align[p])?h.align[p]="left":h.align[p]=null;for(p=0;p<h.cells.length;p++)h.cells[p]=h.cells[p].replace(/^ *\| *| *\| *$/g,"").split(/ *\| */);this.tokens.push(h)}else if(t&&(i=this.rules.paragraph.exec(e)))e=e.substring(i[0].length),this.tokens.push({type:"paragraph",text:"\n"===i[1].charAt(i[1].length-1)?i[1].slice(0,-1):i[1]});else if(i=this.rules.text.exec(e))e=e.substring(i[0].length),this.tokens.push({type:"text",text:i[0]});else if(e)throw new Error("Infinite loop on byte: "+e.charCodeAt(0));return this.tokens};var c={escape:/^\\([\\`*{}\[\]()#+\-.!_>])/,autolink:/^<([^ >]+(@|:\/)[^ >]+)>/,url:h,tag:/^<!--[\s\S]*?-->|^<\/?\w+(?:"[^"]*"|'[^']*'|[^'">])*?>/,link:/^!?\[(inside)\]\(href\)/,reflink:/^!?\[(inside)\]\s*\[([^\]]*)\]/,nolink:/^!?\[((?:\[[^\]]*\]|[^\[\]])*)\]/,strong:/^__([\s\S]+?)__(?!_)|^\*\*([\s\S]+?)\*\*(?!\*)/,em:/^\b_((?:[^_]|__)+?)_\b|^\*((?:\*\*|[\s\S])+?)\*(?!\*)/,code:/^(`+)([\s\S]*?[^`])\1(?!`)/,br:/^ {2,}\n(?!\s*$)/,del:h,text:/^[\s\S]+?(?=[\\<!\[_*`]| {2,}\n|$)/};c._inside=/(?:\[[^\]]*\]|[^\[\]]|\](?=[^\[]*\]))*/,c._href=/\s*<?([\s\S]*?)>?(?:\s+['"]([\s\S]*?)['"])?\s*/,c.link=l(c.link)("inside",c._inside)("href",c._href)(),c.reflink=l(c.reflink)("inside",c._inside)(),c.normal=a({},c),c.pedantic=a({},c.normal,{strong:/^__(?=\S)([\s\S]*?\S)__(?!_)|^\*\*(?=\S)([\s\S]*?\S)\*\*(?!\*)/,em:/^_(?=\S)([\s\S]*?\S)_(?!_)|^\*(?=\S)([\s\S]*?\S)\*(?!\*)/}),c.gfm=a({},c.normal,{escape:l(c.escape)("])","~|])")(),url:/^(https?:\/\/[^\s<]+[^<.,:;"')\]\s])/,del:/^~~(?=\S)([\s\S]*?\S)~~/,text:l(c.text)("]|","~]|")("|","|https?://|")()}),c.breaks=a({},c.gfm,{br:l(c.br)("{2,}","*")(),text:l(c.gfm.text)("{2,}","*")()}),t.rules=c,t.output=function(e,n,r){var s=new t(n,r);return s.output(e)},t.prototype.output=function(e){for(var t,n,r,i,l="";e;)if(i=this.rules.escape.exec(e))e=e.substring(i[0].length),l+=i[1];else if(i=this.rules.autolink.exec(e))e=e.substring(i[0].length),"@"===i[2]?(n=s(":"===i[1].charAt(6)?this.mangle(i[1].substring(7)):this.mangle(i[1])),r=this.mangle("mailto:")+n):(n=s(i[1]),r=n),l+=this.renderer.link(r,null,n);else if(this.inLink||!(i=this.rules.url.exec(e))){if(i=this.rules.tag.exec(e))!this.inLink&&/^<a /i.test(i[0])?this.inLink=!0:this.inLink&&/^<\/a>/i.test(i[0])&&(this.inLink=!1),e=e.substring(i[0].length),l+=this.options.sanitize?this.options.sanitizer?this.options.sanitizer(i[0]):s(i[0]):i[0];else if(i=this.rules.link.exec(e))e=e.substring(i[0].length),this.inLink=!0,l+=this.outputLink(i,{href:i[2],title:i[3]}),this.inLink=!1;else if((i=this.rules.reflink.exec(e))||(i=this.rules.nolink.exec(e))){if(e=e.substring(i[0].length),t=(i[2]||i[1]).replace(/\s+/g," "),t=this.links[t.toLowerCase()],!t||!t.href){l+=i[0].charAt(0),e=i[0].substring(1)+e;continue}this.inLink=!0,l+=this.outputLink(i,t),this.inLink=!1}else if(i=this.rules.strong.exec(e))e=e.substring(i[0].length),l+=this.renderer.strong(this.output(i[2]||i[1]));else if(i=this.rules.em.exec(e))e=e.substring(i[0].length),l+=this.renderer.em(this.output(i[2]||i[1]));else if(i=this.rules.code.exec(e))e=e.substring(i[0].length),l+=this.renderer.codespan(s(i[2].trim(),!0));else if(i=this.rules.br.exec(e))e=e.substring(i[0].length),l+=this.renderer.br();else if(i=this.rules.del.exec(e))e=e.substring(i[0].length),l+=this.renderer.del(this.output(i[1]));else if(i=this.rules.text.exec(e))e=e.substring(i[0].length),l+=this.renderer.text(s(this.smartypants(i[0])));else if(e)throw new Error("Infinite loop on byte: "+e.charCodeAt(0))}else e=e.substring(i[0].length),n=s(i[1]),r=n,l+=this.renderer.link(r,null,n);return l},t.prototype.outputLink=function(e,t){var n=s(t.href),r=t.title?s(t.title):null;return"!"!==e[0].charAt(0)?this.renderer.link(n,r,this.output(e[1])):this.renderer.image(n,r,s(e[1]))},t.prototype.smartypants=function(e){return this.options.smartypants?e.replace(/---/g,"—").replace(/--/g,"–").replace(/(^|[-\u2014\/(\[{"\s])'/g,"$1‘").replace(/'/g,"’").replace(/(^|[-\u2014\/(\[{\u2018\s])"/g,"$1“").replace(/"/g,"”").replace(/\.{3}/g,"…"):e},t.prototype.mangle=function(e){if(!this.options.mangle)return e;for(var t,n="",r=e.length,s=0;s<r;s++)t=e.charCodeAt(s),Math.random()>.5&&(t="x"+t.toString(16)),n+="&#"+t+";";return n},n.prototype.code=function(e,t,n){if(this.options.highlight){var r=this.options.highlight(e,t);null!=r&&r!==e&&(n=!0,e=r)}return t?'<pre><code class="'+this.options.langPrefix+s(t,!0)+'">'+(n?e:s(e,!0))+"\n</code></pre>\n":"<pre><code>"+(n?e:s(e,!0))+"\n</code></pre>"},n.prototype.blockquote=function(e){return"<blockquote>\n"+e+"</blockquote>\n"},n.prototype.html=function(e){return e},n.prototype.heading=function(e,t,n){return"<h"+t+' id="'+this.options.headerPrefix+n.toLowerCase().replace(/[^\w]+/g,"-")+'">'+e+"</h"+t+">\n"},n.prototype.hr=function(){return this.options.xhtml?"<hr/>\n":"<hr>\n"},n.prototype.list=function(e,t){var n=t?"ol":"ul";return"<"+n+">\n"+e+"</"+n+">\n"},n.prototype.listitem=function(e){return"<li>"+e+"</li>\n"},n.prototype.paragraph=function(e){return"<p>"+e+"</p>\n"},n.prototype.table=function(e,t){return"<table>\n<thead>\n"+e+"</thead>\n<tbody>\n"+t+"</tbody>\n</table>\n"},n.prototype.tablerow=function(e){return"<tr>\n"+e+"</tr>\n"},n.prototype.tablecell=function(e,t){var n=t.header?"th":"td",r=t.align?"<"+n+' style="text-align:'+t.align+'">':"<"+n+">";return r+e+"</"+n+">\n"},n.prototype.strong=function(e){return"<strong>"+e+"</strong>"},n.prototype.em=function(e){return"<em>"+e+"</em>"},n.prototype.codespan=function(e){return"<code>"+e+"</code>"},n.prototype.br=function(){return this.options.xhtml?"<br/>":"<br>"},n.prototype.del=function(e){return"<del>"+e+"</del>"},n.prototype.link=function(e,t,n){if(this.options.sanitize){try{var r=decodeURIComponent(i(e)).replace(/[^\w:]/g,"").toLowerCase()}catch(s){return""}if(0===r.indexOf("javascript:")||0===r.indexOf("vbscript:")||0===r.indexOf("data:"))return""}this.options.baseUrl&&!originIndependentUrl.test(e)&&(e=o(this.options.baseUrl,e));var l='<a href="'+e+'"';return t&&(l+=' title="'+t+'"'),l+=">"+n+"</a>"},n.prototype.image=function(e,t,n){this.options.baseUrl&&!originIndependentUrl.test(e)&&(e=o(this.options.baseUrl,e));var r='<img src="'+e+'" alt="'+n+'"';return t&&(r+=' title="'+t+'"'),r+=this.options.xhtml?"/>":">"},n.prototype.text=function(e){return e},r.parse=function(e,t,n){var s=new r(t,n);return s.parse(e)},r.prototype.parse=function(e){this.inline=new t(e.links,this.options,this.renderer),this.tokens=e.reverse();for(var n="";this.next();)n+=this.tok();return n},r.prototype.next=function(){return this.token=this.tokens.pop()},r.prototype.peek=function(){return this.tokens[this.tokens.length-1]||0},r.prototype.parseText=function(){for(var e=this.token.text;"text"===this.peek().type;)e+="\n"+this.next().text;return this.inline.output(e)},r.prototype.tok=function(){switch(this.token.type){case"space":return"";case"hr":return this.renderer.hr();case"heading":return this.renderer.heading(this.inline.output(this.token.text),this.token.depth,this.token.text);case"code":return this.renderer.code(this.token.text,this.token.lang,this.token.escaped);case"table":var e,t,n,r,s,i="",l="";for(n="",e=0;e<this.token.header.length;e++)r={header:!0,align:this.token.align[e]},n+=this.renderer.tablecell(this.inline.output(this.token.header[e]),{header:!0,align:this.token.align[e]});for(i+=this.renderer.tablerow(n),e=0;e<this.token.cells.length;e++){for(t=this.token.cells[e],n="",s=0;s<t.length;s++)n+=this.renderer.tablecell(this.inline.output(t[s]),{header:!1,align:this.token.align[s]});l+=this.renderer.tablerow(n)}return this.renderer.table(i,l);case"blockquote_start":for(var l="";"blockquote_end"!==this.next().type;)l+=this.tok();return this.renderer.blockquote(l);case"list_start":for(var l="",o=this.token.ordered;"list_end"!==this.next().type;)l+=this.tok();return this.renderer.list(l,o);case"list_item_start":for(var l="";"list_item_end"!==this.next().type;)l+="text"===this.token.type?this.parseText():this.tok();return this.renderer.listitem(l);case"loose_item_start":for(var l="";"list_item_end"!==this.next().type;)l+=this.tok();return this.renderer.listitem(l);case"html":var h=this.token.pre||this.options.pedantic?this.token.text:this.inline.output(this.token.text);return this.renderer.html(h);case"paragraph":return this.renderer.paragraph(this.inline.output(this.token.text));case"text":return this.renderer.paragraph(this.parseText())}},baseUrls={},originIndependentUrl=/^$|^[a-z][a-z0-9+.-]*:|^[?#]/i,h.exec=h,p.options=p.setOptions=function(e){return a(p.defaults,e),p},p.defaults={gfm:!0,tables:!0,breaks:!1,pedantic:!1,sanitize:!1,sanitizer:null,mangle:!0,smartLists:!1,silent:!1,highlight:null,langPrefix:"lang-",smartypants:!1,headerPrefix:"",renderer:new n,xhtml:!1,baseUrl:null},p.Parser=r,p.parser=r.parse,p.Renderer=n,p.Lexer=e,p.lexer=e.lex,p.InlineLexer=t,p.inlineLexer=t.output,p.parse=p,"undefined"!=typeof module&&"object"==typeof exports?module.exports=p:"function"==typeof define&&define.amd?define(function(){return p}):this.marked=p}).call(function(){return this||("undefined"!=typeof window?window:global)}()); \ No newline at end of file
diff --git a/editor/public/strftime.min.js b/editor/public/strftime.min.js
new file mode 100644
index 0000000..88dd19a
--- /dev/null
+++ b/editor/public/strftime.min.js
@@ -0,0 +1,23 @@
+(function(){function q(c,g,n){function i(c,a,e,f){for(var b="",d=null,g=!1,l=c.length,n=!1,j=0;j<l;j++){var m=c.charCodeAt(j);if(g===!0)if(m===45)d="";else if(m===95)d=" ";else if(m===48)d="0";else if(m===58)n&&t("[WARNING] detected use of unsupported %:: or %::: modifiers to strftime"),n=!0;else{switch(m){case 37:b+="%";break;case 65:b+=e.days[a.getDay()];break;case 66:b+=e.months[a.getMonth()];break;case 67:b+=h(Math.floor(a.getFullYear()/100),d);break;case 68:b+=i(e.formats.D,a,e,f);break;case 70:b+=
+i(e.formats.F,a,e,f);break;case 72:b+=h(a.getHours(),d);break;case 73:b+=h(u(a.getHours()),d);break;case 76:b+=Math.floor(f%1E3)>99?Math.floor(f%1E3):Math.floor(f%1E3)>9?"0"+Math.floor(f%1E3):"00"+Math.floor(f%1E3);break;case 77:b+=h(a.getMinutes(),d);break;case 80:b+=a.getHours()<12?e.am:e.pm;break;case 82:b+=i(e.formats.R,a,e,f);break;case 83:b+=h(a.getSeconds(),d);break;case 84:b+=i(e.formats.T,a,e,f);break;case 85:b+=h(v(a,"sunday"),d);break;case 87:b+=h(v(a,"monday"),d);break;case 88:b+=i(e.formats.X,
+a,e,f);break;case 89:b+=a.getFullYear();break;case 90:o&&k===0?b+="GMT":(d=a.toString().match(/\(([\w\s]+)\)/),b+=d&&d[1]||"");break;case 97:b+=e.shortDays[a.getDay()];break;case 98:b+=e.shortMonths[a.getMonth()];break;case 99:b+=i(e.formats.c,a,e,f);break;case 100:b+=h(a.getDate(),d);break;case 101:b+=h(a.getDate(),d==null?" ":d);break;case 104:b+=e.shortMonths[a.getMonth()];break;case 106:d=new Date(a.getFullYear(),0,1);d=Math.ceil((a.getTime()-d.getTime())/864E5);b+=d>99?d:d>9?"0"+d:"00"+d;break;
+case 107:b+=h(a.getHours(),d==null?" ":d);break;case 108:b+=h(u(a.getHours()),d==null?" ":d);break;case 109:b+=h(a.getMonth()+1,d);break;case 110:b+="\n";break;case 111:d=a.getDate();b+=e.ordinalSuffixes?String(d)+(e.ordinalSuffixes[d-1]||w(d)):String(d)+w(d);break;case 112:b+=a.getHours()<12?e.AM:e.PM;break;case 114:b+=i(e.formats.r,a,e,f);break;case 115:b+=Math.floor(f/1E3);break;case 116:b+="\t";break;case 117:d=a.getDay();b+=d===0?7:d;break;case 118:b+=i(e.formats.v,a,e,f);break;case 119:b+=a.getDay();
+break;case 120:b+=i(e.formats.x,a,e,f);break;case 121:b+=(""+a.getFullYear()).slice(2);break;case 122:o&&k===0?b+=n?"+00:00":"+0000":(d=k!==0?k/6E4:-a.getTimezoneOffset(),g=n?":":"",m=Math.abs(d%60),b+=(d<0?"-":"+")+h(Math.floor(Math.abs(d/60)))+g+h(m));break;default:g&&(b+="%"),b+=c[j]}d=null;g=!1}else m===37?g=!0:b+=c[j]}return b}var j=c||x,k=g||0,o=n||!1,p=0,r,l=function(c,a){var e;if(a){if(e=a.getTime(),o){var f=(a.getTimezoneOffset()||0)*6E4,a=new Date(e+f+k);if((a.getTimezoneOffset()||0)*6E4!==
+f)f=(a.getTimezoneOffset()||0)*6E4,a=new Date(e+f+k)}}else e=Date.now(),e>p?(p=e,r=new Date(p),e=p,o&&(r=new Date(p+(r.getTimezoneOffset()||0)*6E4+k))):e=p,a=r;return i(c,a,j,e)};l.localize=function(c){return new q(c||j,k,o)};l.localizeByIdentifier=function(c){var a=y[c];return!a?(t('[WARNING] No locale found with identifier "'+c+'".'),l):l.localize(a)};l.timezone=function(c){var a=k,e=o,f=typeof c;if(f==="number"||f==="string")e=!0,f==="string"?(a=c[0]==="-"?-1:1,f=parseInt(c.slice(1,3),10),c=parseInt(c.slice(3,
+5),10),a=a*(60*f+c)*6E4):f==="number"&&(a=c*6E4);return new q(j,a,e)};l.utc=function(){return new q(j,k,!0)};return l}function h(c,g){if(g===""||c>9)return c;g==null&&(g="0");return g+c}function u(c){if(c===0)return 12;else if(c>12)return c-12;return c}function v(c,g){var g=g||"sunday",h=c.getDay();g==="monday"&&(h===0?h=6:h--);var i=Date.UTC(c.getFullYear(),0,1),j=Date.UTC(c.getFullYear(),c.getMonth(),c.getDate());return Math.floor((Math.floor((j-i)/864E5)+7-h)/7)}function w(c){var g=c%10;c%=100;
+if(c>=11&&c<=13||g===0||g>=4)return"th";switch(g){case 1:return"st";case 2:return"nd";case 3:return"rd"}}function t(c){typeof console!=="undefined"&&typeof console.warn=="function"&&console.warn(c)}var y={de_DE:{days:["Sonntag","Montag","Dienstag","Mittwoch","Donnerstag","Freitag","Samstag"],shortDays:["So","Mo","Di","Mi","Do","Fr","Sa"],months:["Januar","Februar","M\u00e4rz","April","Mai","Juni","Juli","August","September","Oktober","November","Dezember"],shortMonths:["Jan","Feb","M\u00e4r","Apr",
+"Mai","Jun","Jul","Aug","Sep","Okt","Nov","Dez"],AM:"AM",PM:"PM",am:"am",pm:"pm",formats:{c:"%a %d %b %Y %X %Z",D:"%d.%m.%Y",F:"%Y-%m-%d",R:"%H:%M",r:"%I:%M:%S %p",T:"%H:%M:%S",v:"%e-%b-%Y",X:"%T",x:"%D"}},en_CA:{days:["Sunday","Monday","Tuesday","Wednesday","Thursday","Friday","Saturday"],shortDays:["Sun","Mon","Tue","Wed","Thu","Fri","Sat"],months:["January","February","March","April","May","June","July","August","September","October","November","December"],shortMonths:["Jan","Feb","Mar","Apr",
+"May","Jun","Jul","Aug","Sep","Oct","Nov","Dec"],ordinalSuffixes:["st","nd","rd","th","th","th","th","th","th","th","th","th","th","th","th","th","th","th","th","th","st","nd","rd","th","th","th","th","th","th","th","st"],AM:"AM",PM:"PM",am:"am",pm:"pm",formats:{c:"%a %d %b %Y %X %Z",D:"%d/%m/%y",F:"%Y-%m-%d",R:"%H:%M",r:"%I:%M:%S %p",T:"%H:%M:%S",v:"%e-%b-%Y",X:"%r",x:"%D"}},en_US:{days:["Sunday","Monday","Tuesday","Wednesday","Thursday","Friday","Saturday"],shortDays:["Sun","Mon","Tue","Wed","Thu",
+"Fri","Sat"],months:["January","February","March","April","May","June","July","August","September","October","November","December"],shortMonths:["Jan","Feb","Mar","Apr","May","Jun","Jul","Aug","Sep","Oct","Nov","Dec"],ordinalSuffixes:["st","nd","rd","th","th","th","th","th","th","th","th","th","th","th","th","th","th","th","th","th","st","nd","rd","th","th","th","th","th","th","th","st"],AM:"AM",PM:"PM",am:"am",pm:"pm",formats:{c:"%a %d %b %Y %X %Z",D:"%m/%d/%y",F:"%Y-%m-%d",R:"%H:%M",r:"%I:%M:%S %p",
+T:"%H:%M:%S",v:"%e-%b-%Y",X:"%r",x:"%D"}},es_MX:{days:["domingo","lunes","martes","mi\u00e9rcoles","jueves","viernes","s\u00e1bado"],shortDays:["dom","lun","mar","mi\u00e9","jue","vie","s\u00e1b"],months:["enero","febrero","marzo","abril","mayo","junio","julio","agosto","septiembre","octubre","noviembre"," diciembre"],shortMonths:["ene","feb","mar","abr","may","jun","jul","ago","sep","oct","nov","dic"],AM:"AM",PM:"PM",am:"am",pm:"pm",formats:{c:"%a %d %b %Y %X %Z",D:"%d/%m/%Y",F:"%Y-%m-%d",R:"%H:%M",
+r:"%I:%M:%S %p",T:"%H:%M:%S",v:"%e-%b-%Y",X:"%T",x:"%D"}},fr_FR:{days:["dimanche","lundi","mardi","mercredi","jeudi","vendredi","samedi"],shortDays:["dim.","lun.","mar.","mer.","jeu.","ven.","sam."],months:["janvier","f\u00e9vrier","mars","avril","mai","juin","juillet","ao\u00fbt","septembre","octobre","novembre","d\u00e9cembre"],shortMonths:["janv.","f\u00e9vr.","mars","avril","mai","juin","juil.","ao\u00fbt","sept.","oct.","nov.","d\u00e9c."],AM:"AM",PM:"PM",am:"am",pm:"pm",formats:{c:"%a %d %b %Y %X %Z",
+D:"%d/%m/%Y",F:"%Y-%m-%d",R:"%H:%M",r:"%I:%M:%S %p",T:"%H:%M:%S",v:"%e-%b-%Y",X:"%T",x:"%D"}},it_IT:{days:["domenica","luned\u00ec","marted\u00ec","mercoled\u00ec","gioved\u00ec","venerd\u00ec","sabato"],shortDays:["dom","lun","mar","mer","gio","ven","sab"],months:["gennaio","febbraio","marzo","aprile","maggio","giugno","luglio","agosto","settembre","ottobre","novembre","dicembre"],shortMonths:["pr","mag","giu","lug","ago","set","ott","nov","dic"],AM:"AM",PM:"PM",am:"am",pm:"pm",formats:{c:"%a %d %b %Y %X %Z",
+D:"%d/%m/%Y",F:"%Y-%m-%d",R:"%H:%M",r:"%I:%M:%S %p",T:"%H:%M:%S",v:"%e-%b-%Y",X:"%T",x:"%D"}},nl_NL:{days:["zondag","maandag","dinsdag","woensdag","donderdag","vrijdag","zaterdag"],shortDays:["zo","ma","di","wo","do","vr","za"],months:["januari","februari","maart","april","mei","juni","juli","augustus","september","oktober","november","december"],shortMonths:["jan","feb","mrt","apr","mei","jun","jul","aug","sep","okt","nov","dec"],AM:"AM",PM:"PM",am:"am",pm:"pm",formats:{c:"%a %d %b %Y %X %Z",D:"%d-%m-%y",
+F:"%Y-%m-%d",R:"%H:%M",r:"%I:%M:%S %p",T:"%H:%M:%S",v:"%e-%b-%Y",X:"%T",x:"%D"}},pt_BR:{days:["domingo","segunda","ter\u00e7a","quarta","quinta","sexta","s\u00e1bado"],shortDays:["Dom","Seg","Ter","Qua","Qui","Sex","S\u00e1b"],months:["janeiro","fevereiro","mar\u00e7o","abril","maio","junho","julho","agosto","setembro","outubro","novembro","dezembro"],shortMonths:["Jan","Fev","Mar","Abr","Mai","Jun","Jul","Ago","Set","Out","Nov","Dez"],AM:"AM",PM:"PM",am:"am",pm:"pm",formats:{c:"%a %d %b %Y %X %Z",
+D:"%d-%m-%Y",F:"%Y-%m-%d",R:"%H:%M",r:"%I:%M:%S %p",T:"%H:%M:%S",v:"%e-%b-%Y",X:"%T",x:"%D"}},ru_RU:{days:["\u0412\u043e\u0441\u043a\u0440\u0435\u0441\u0435\u043d\u044c\u0435","\u041f\u043e\u043d\u0435\u0434\u0435\u043b\u044c\u043d\u0438\u043a","\u0412\u0442\u043e\u0440\u043d\u0438\u043a","\u0421\u0440\u0435\u0434\u0430","\u0427\u0435\u0442\u0432\u0435\u0440\u0433","\u041f\u044f\u0442\u043d\u0438\u0446\u0430","\u0421\u0443\u0431\u0431\u043e\u0442\u0430"],shortDays:["\u0412\u0441","\u041f\u043d","\u0412\u0442",
+"\u0421\u0440","\u0427\u0442","\u041f\u0442","\u0421\u0431"],months:["\u042f\u043d\u0432\u0430\u0440\u044c","\u0424\u0435\u0432\u0440\u0430\u043b\u044c","\u041c\u0430\u0440\u0442","\u0410\u043f\u0440\u0435\u043b\u044c","\u041c\u0430\u0439","\u0418\u044e\u043d\u044c","\u0418\u044e\u043b\u044c","\u0410\u0432\u0433\u0443\u0441\u0442","\u0421\u0435\u043d\u0442\u044f\u0431\u0440\u044c","\u041e\u043a\u0442\u044f\u0431\u0440\u044c","\u041d\u043e\u044f\u0431\u0440\u044c","\u0414\u0435\u043a\u0430\u0431\u0440\u044c"],
+shortMonths:["\u044f\u043d\u0432","\u0444\u0435\u0432","\u043c\u0430\u0440","\u0430\u043f\u0440","\u043c\u0430\u0439","\u0438\u044e\u043d","\u0438\u044e\u043b","\u0430\u0432\u0433","\u0441\u0435\u043d","\u043e\u043a\u0442","\u043d\u043e\u044f","\u0434\u0435\u043a"],AM:"AM",PM:"PM",am:"am",pm:"pm",formats:{c:"%a %d %b %Y %X",D:"%d.%m.%y",F:"%Y-%m-%d",R:"%H:%M",r:"%I:%M:%S %p",T:"%H:%M:%S",v:"%e-%b-%Y",X:"%T",x:"%D"}},tr_TR:{days:["Pazar","Pazartesi","Sal\u0131","\u00c7ar\u015famba","Per\u015fembe",
+"Cuma","Cumartesi"],shortDays:["Paz","Pzt","Sal","\u00c7r\u015f","Pr\u015f","Cum","Cts"],months:["Ocak","\u015eubat","Mart","Nisan","May\u0131s","Haziran","Temmuz","A\u011fustos","Eyl\u00fcl","Ekim","Kas\u0131m","Aral\u0131k"],shortMonths:["Oca","\u015eub","Mar","Nis","May","Haz","Tem","A\u011fu","Eyl","Eki","Kas","Ara"],AM:"\u00d6\u00d6",PM:"\u00d6S",am:"\u00d6\u00d6",pm:"\u00d6S",formats:{c:"%a %d %b %Y %X %Z",D:"%d-%m-%Y",F:"%Y-%m-%d",R:"%H:%M",r:"%I:%M:%S %p",T:"%H:%M:%S",v:"%e-%b-%Y",X:"%T",
+x:"%D"}},zh_CN:{days:["\u661f\u671f\u65e5","\u661f\u671f\u4e00","\u661f\u671f\u4e8c","\u661f\u671f\u4e09","\u661f\u671f\u56db","\u661f\u671f\u4e94","\u661f\u671f\u516d"],shortDays:["\u65e5","\u4e00","\u4e8c","\u4e09","\u56db","\u4e94","\u516d"],months:["\u4e00\u6708\u4efd","\u4e8c\u6708\u4efd","\u4e09\u6708\u4efd","\u56db\u6708\u4efd","\u4e94\u6708\u4efd","\u516d\u6708\u4efd","\u4e03\u6708\u4efd","\u516b\u6708\u4efd","\u4e5d\u6708\u4efd","\u5341\u6708\u4efd","\u5341\u4e00\u6708\u4efd","\u5341\u4e8c\u6708\u4efd"],
+shortMonths:["\u4e00\u6708","\u4e8c\u6708","\u4e09\u6708","\u56db\u6708","\u4e94\u6708","\u516d\u6708","\u4e03\u6708","\u516b\u6708","\u4e5d\u6708","\u5341\u6708","\u5341\u4e00\u6708","\u5341\u4e8c\u6708"],AM:"\u4e0a\u5348",PM:"\u4e0b\u5348",am:"\u4e0a\u5348",pm:"\u4e0b\u5348",formats:{c:"%a %d %b %Y %X %Z",D:"%d/%m/%y",F:"%Y-%m-%d",R:"%H:%M",r:"%I:%M:%S %p",T:"%H:%M:%S",v:"%e-%b-%Y",X:"%r",x:"%D"}}},x=y.en_US,z=new q(x,0,!1),s;typeof module!=="undefined"?s=module.exports=z:(s=function(){return this||
+(0,eval)("this")}(),s.strftime=z);if(typeof Date.now!=="function")Date.now=function(){return+new Date}})(); \ No newline at end of file
diff --git a/generate-tags.js b/generate-tags.js
new file mode 100644
index 0000000..1b4b8f8
--- /dev/null
+++ b/generate-tags.js
@@ -0,0 +1,45 @@
+const mkdirp = require("mkdirp");
+const path = require("path");
+
+const saveIndex = require("./save-index.js");
+
+function generateTags(articles) {
+ const articlesByTag = { };
+ articles.forEach(article => {
+ article.tags.forEach(tag => {
+ if (!articlesByTag[tag]) {
+ articlesByTag[tag] = [article];
+ } else {
+ articlesByTag[tag].push(article);
+ }
+ });
+ });
+
+ const promises = Object.keys(articlesByTag).map(
+ tag =>
+ new Promise((resolve, reject) => {
+ const articles = articlesByTag[tag];
+ articles.sort((a, b) => {
+ return (b.rawDate || 0) - (a.rawDate || 0);
+ });
+
+ const tagPath = path.join("output", "tags", tag);
+ mkdirp(tagPath, err => {
+ if (err) { return reject(err); }
+
+ resolve(
+ saveIndex(
+ articles.filter(article => !article.hidden),
+ path.join(tagPath, "index.html"),
+ tag + " | jordan scales",
+ "-- " + tag + " posts --"
+ )
+ );
+ });
+ })
+ );
+
+ return Promise.all(promises);
+}
+
+module.exports = generateTags;
diff --git a/hidden/2013-08-05-seriously-the-reflog-isnt-that-scary.md b/hidden/2013-08-05-seriously-the-reflog-isnt-that-scary.md
new file mode 100644
index 0000000..f3ac342
--- /dev/null
+++ b/hidden/2013-08-05-seriously-the-reflog-isnt-that-scary.md
@@ -0,0 +1,320 @@
+---
+title: Seriously, The Reflog Isn't That Scary
+date: 2013-08-05
+route: /seriously-the-reflog-isnt-that-scary
+color: green
+description: demystifying git-reflog
+hidden: true
+tags: git
+---
+
+I know, I know - changing history can be scary. Rebasing, squashing, and loads of force pushing can worry any junior engineer such as myself, but it really shouldn't. Truth is, these are powerful tools that you should have on your toolbelt and, contrary to what you may believe, it's very difficult to actually mess things up!
+
+This guide exhibits my own experience with using the reflog to fix stupid mistakes I've made on the command line, and is divided into three sections.
+
+- [Undoing amended commits](#undo-amend)
+- [Reverting bad merges](#undo-merge)
+- [Recovering hard resets](#undo-hard-reset)
+
+### What is the reflog?
+
+The [reflog](https://www.kernel.org/pub/software/scm/git/docs/git-reflog.html) is a special mechanism (which acts very similarly to a branch) that contains any changes to the data in your repository. This includes commiting changes, creating and checking out branches, and even hard resets. It's a handy timeline, going a step further from the usual history tree, that you can use to recover any data you may have misplaced.
+
+Let's walk through a couple of use-cases on a [real repository](https://github.com/jdan/reflog-example).
+
+ $ git init
+ Initialized empty Git repository in /Users/jordan/Projects/reflog-example/.git/
+ $ echo "Hello, world" > hello.txt
+ $ git add .
+ $ git commit -m "initial commit."
+ [master (root-commit) 9056e55] initial commit.
+ 1 file changed, 1 insertion(+)
+ create mode 100644 hello.txt
+
+<a id="undo-amend"></a>
+
+### Example A: Undoing an Amended Commit
+
+`git commit --amend` is a great way to consolidate your work. Missed a semicolon? No need to make an entirely new commit for that, just amend the previous one! But, what if we want to **undo** an amend to a commit? We can't revert a particular commit, since we only want to remove _some_ of the changes in a commit. Seems tricky, but it really isn't. Let's start by amending our initial commit.
+
+ $ echo "Goodnight, moon" >> hello.txt
+ $ git add .
+ $ git commit --amend
+ [master e1a2208] initial commit.
+ 1 file changed, 2 insertions(+)
+ create mode 100644 hello.txt
+
+Now if we view our commit, we'll see two additions at the bottom.
+
+ $ git show HEAD
+ commit e1a22086fda87daff79fe2bf179372a46abc3044
+ Author: Jordan Scales <[email protected]>
+ Date: Sun Aug 4 22:46:34 2013 -0700
+
+ initial commit.
+
+ diff --git a/hello.txt b/hello.txt
+ new file mode 100644
+ index 0000000..f48e806
+ --- /dev/null
+ +++ b/hello.txt
+ @@ -0,0 +1,2 @@
+ +Hello, world
+ +Goodnight, moon
+
+But what if we wanted to split this up into two commits? This example is trivial, but in the real world you may accidentally amend 100 changes to a previous commit (with 100 changes of its own!) We don't want them combined, so how can we separate them? **This is where the reflog comes into play**. Let's see what we get when we run `git reflog`.
+
+ $ git log
+ commit e1a22086fda87daff79fe2bf179372a46abc3044
+ Author: Jordan Scales <[email protected]>
+ Date: Sun Aug 4 22:46:34 2013 -0700
+
+ initial commit.
+ $ git reflog
+ e1a2208 HEAD@{0}: commit (amend): initial commit.
+ 9056e55 HEAD@{1}: commit (initial): initial commit.
+
+While `git log` only has one entry (which makes sense considering we only have one commit), `git reflog` actually consists of _two_ entries. This is awesome, we now have some extra material to work with. Notice how the left column looks suspiciously similar to a log? Instead of commits, we're dealing with **reflog entries**, but they behave just like commits on a branch. To demonstrate this, let's go ahead and do a soft reset.
+
+ $ git reset --soft 9056e55
+ $ git diff --staged
+ diff --git a/hello.txt b/hello.txt
+ index a5c1966..f48e806 100644
+ --- a/hello.txt
+ +++ b/hello.txt
+ @@ -1 +1,2 @@
+ Hello, world
+ +Goodnight, moon
+
+Awesome. Now our staged changes consist of just the second addition we made on the master branch. Let's verify that our first change is still in tact.
+
+ $ git show HEAD
+ commit 9056e55e384cbae33a0aaf9b2a861cb20c20ace0
+ Author: Jordan Scales <[email protected]>
+ Date: Sun Aug 4 22:46:34 2013 -0700
+
+ initial commit.
+
+ diff --git a/hello.txt b/hello.txt
+ new file mode 100644
+ index 0000000..a5c1966
+ --- /dev/null
+ +++ b/hello.txt
+ @@ -0,0 +1 @@
+ +Hello, world
+
+Now, let's go ahead and commit our changes.
+
+ $ git commit -m "Second commit"
+ [master d9b0af2] Second commit
+ 1 file changed, 1 insertion(+)
+ $ git log
+ commit d9b0af228fa2f4e9ac0c453faf5a652b5ccfa55e
+ Author: Jordan Scales <[email protected]>
+ Date: Sun Aug 4 22:58:55 2013 -0700
+
+ Second commit
+
+ commit 9056e55e384cbae33a0aaf9b2a861cb20c20ace0
+ Author: Jordan Scales <[email protected]>
+ Date: Sun Aug 4 22:46:34 2013 -0700
+
+ initial commit.
+
+We've successfully separated an amended commit into two separate commits.
+
+You'll notice that the reflog itself reflects the changes we've just made, in case we want to go back yet again.
+
+ $ git reflog
+ d9b0af2 HEAD@{0}: commit: Second commit
+ 9056e55 HEAD@{1}: reset: moving to 9056e55
+ e1a2208 HEAD@{2}: commit (amend): initial commit.
+ 9056e55 HEAD@{3}: commit (initial): initial commit.
+
+<a id="undo-merge"></a>
+
+### Example B: Rolling Back a Bad Merge
+
+Let's say we've merged some changes from a branch. If our correspondent worked alongside master for quite some time and never [cleaned up](http://blog.steveklabnik.com/posts/2012-11-08-how-to-squash-commits-in-a-github-pull-request) his or her changes, we could be dealing with a messy rollback. Let's see how we would resolve this issue using the reflog.
+
+ $ git merge my-awesome-branch
+ Updating d9b0af2..29ef6ec
+ Fast-forward
+ hello.txt | 3 +++
+ 1 file changed, 3 insertions(+)
+ $ git log
+ commit 29ef6ec17401576b7be9ecfd3cf8eeecb8e26288
+ Author: Jordan Scales <[email protected]>
+ Date: Sun Aug 4 23:07:41 2013 -0700
+
+ Changes on another branch
+ ...
+
+Looking at reflog we should see a nice summary of our recent activity.
+
+ $ git branch -D my-awesome-branch
+ Deleted branch my-awesome-branch (was 29ef6ec).
+ $ git reflog
+ 29ef6ec HEAD@{0}: merge my-awesome-branch: Fast-forward
+ d9b0af2 HEAD@{1}: checkout: moving from my-awesome-branch to master
+ 29ef6ec HEAD@{2}: commit: Changes on another branch
+ d9b0af2 HEAD@{3}: checkout: moving from master to my-awesome-branch
+ d9b0af2 HEAD@{4}: commit: Second commit
+ 9056e55 HEAD@{5}: reset: moving to 9056e55
+ e1a2208 HEAD@{6}: commit (amend): initial commit.
+ 9056e55 HEAD@{7}: commit (initial): initial commit.
+
+We have a few options here. We can revert back to a time before the branch was even created, when the commit was made on the branch, or just before we merged into master. For our use-case, we simply want to undo our merge. `d9b0af2` looks promising (but there are other equally good choices here). This time, we'll do a _hard_ reset, since we don't need to stage any changes before a merge.
+
+ $ git reset --hard d9b0af2
+ HEAD is now at d9b0af2 Second commit
+ $ git log
+ commit d9b0af228fa2f4e9ac0c453faf5a652b5ccfa55e
+ Author: Jordan Scales <[email protected]>
+ Date: Sun Aug 4 22:58:55 2013 -0700
+
+ Second commit
+
+ commit 9056e55e384cbae33a0aaf9b2a861cb20c20ace0
+ Author: Jordan Scales <[email protected]>
+ Date: Sun Aug 4 22:46:34 2013 -0700
+
+ initial commit.
+
+Looks good! But now, what if we wanted to go back to our deleted branch?
+
+ $ git branch
+ * master
+ $ git co my-awesome-branch
+ error: pathspec 'my-awesome-branch' did not match any file(s) known to git.
+
+Well that's kind of a bummer. According to our previous example, we expect the reflog to indicate a point in time where we were working on a separate unmerged branch. What gives? Let's take another gander at the reflog.
+
+ $ git reflog
+ d9b0af2 HEAD@{0}: reset: moving to d9b0af2
+ 29ef6ec HEAD@{1}: merge my-awesome-branch: Fast-forward
+ d9b0af2 HEAD@{2}: checkout: moving from my-awesome-branch to master
+ 29ef6ec HEAD@{3}: commit: Changes on another branch
+ d9b0af2 HEAD@{4}: checkout: moving from master to my-awesome-branch
+ d9b0af2 HEAD@{5}: commit: Second commit
+ 9056e55 HEAD@{6}: reset: moving to 9056e55
+ e1a2208 HEAD@{7}: commit (amend): initial commit.
+ 9056e55 HEAD@{8}: commit (initial): initial commit.
+
+Hm, `29ef6ec` indicates a commit on our branch. Let's explore.
+
+ $ git co 29ef6ec
+ Note: checking out '29ef6ec'.
+ ...
+ HEAD is now at 29ef6ec... Changes on another branch
+ $ git log
+ commit 29ef6ec17401576b7be9ecfd3cf8eeecb8e26288
+ Author: Jordan Scales <[email protected]>
+ Date: Sun Aug 4 23:07:41 2013 -0700
+
+ Changes on another branch
+ ...
+
+This looks promising! Let's go ahead and make a branch here.
+
+ $ git co -b my-awesome-branch
+ Switched to a new branch 'my-awesome-branch'
+ $ git log
+ commit 29ef6ec17401576b7be9ecfd3cf8eeecb8e26288
+ Author: Jordan Scales <[email protected]>
+ Date: Sun Aug 4 23:07:41 2013 -0700
+
+ Changes on another branch
+ ...
+
+Success! We've now successfully recovered our branch. How about master? Is our rogue commit still separate from the master branch?
+
+ $ git co master
+ Switched to branch 'master'
+ $ git log
+ commit d9b0af228fa2f4e9ac0c453faf5a652b5ccfa55e
+ Author: Jordan Scales <[email protected]>
+ Date: Sun Aug 4 22:58:55 2013 -0700
+
+ Second commit
+
+Yep.
+
+<a id="undo-hard-reset"></a>
+
+### Example C: Recovering from a Hard Reset
+
+At this point you should be fairly comfortable with the reflog, and can now apply it to many different situations. To really nail it down, let's go through one more common example: recovering the data from a hard reset. Just as a refresher, consider the state of our master branch.
+
+ $ git log
+ commit d9b0af228fa2f4e9ac0c453faf5a652b5ccfa55e
+ Author: Jordan Scales <[email protected]>
+ Date: Sun Aug 4 22:58:55 2013 -0700
+
+ Second commit
+
+ commit 9056e55e384cbae33a0aaf9b2a861cb20c20ace0
+ Author: Jordan Scales <[email protected]>
+ Date: Sun Aug 4 22:46:34 2013 -0700
+
+ initial commit.
+
+If we wanted to completely wipe the most recent commit - "Second commit" - we can do that rather easily.
+
+ $ git reset --hard 9056e55e384cbae33a0aaf9b2a861cb20c20ace0
+ HEAD is now at 9056e55 initial commit.
+ $ git log
+ commit 9056e55e384cbae33a0aaf9b2a861cb20c20ace0
+ Author: Jordan Scales <[email protected]>
+ Date: Sun Aug 4 22:46:34 2013 -0700
+
+ initial commit.
+
+And voilà, it's gone. But, what if that commit had some important data in it? What if we were careless with our hard resets, and accidentally removed important changes? Sometimes this really happens, and we can use the reflog to our advantage and recover the lost changes.
+
+Once again, let's consult the reflog.
+
+ $ git reflog
+ 9056e55 HEAD@{0}: reset: moving to 9056e55e384cbae33a0aaf9b2a861cb20c20ace0
+ d9b0af2 HEAD@{1}: checkout: moving from save-branch to master
+ 29ef6ec HEAD@{2}: checkout: moving from 29ef6ec17401576b7be9ecfd3cf8eeecb8e26288 to save-branch
+ 29ef6ec HEAD@{3}: checkout: moving from master to 29ef6ec
+ d9b0af2 HEAD@{4}: reset: moving to d9b0af2
+ 29ef6ec HEAD@{5}: checkout: moving from my-awesome-branch to master
+ ...
+
+Here we'll see lots of data, but we're only considered with the first two entries: the current state, and the previous checkout.
+
+> As a sidenode, feel free to check out [reflog, your safety net](http://gitready.com/intermediate/2009/02/09/reflog-your-safety-net.html) on git ready, which goes on to explain how to clean up old entries in your reflog.
+
+Now, all we need to do is trek back to that previous entry. Easy stuff.
+
+ $ git reset --hard d9b0af2
+ HEAD is now at d9b0af2 Second commit
+ $ git log
+ commit d9b0af228fa2f4e9ac0c453faf5a652b5ccfa55e
+ Author: Jordan Scales <[email protected]>
+ Date: Sun Aug 4 22:58:55 2013 -0700
+
+ Second commit
+
+ commit 9056e55e384cbae33a0aaf9b2a861cb20c20ace0
+ Author: Jordan Scales <[email protected]>
+ Date: Sun Aug 4 22:46:34 2013 -0700
+
+ initial commit.
+
+And there you have it, we've successfully recovered data from a hard reset.
+
+<a id="closing-notes"></a>
+
+### Closing Notes
+
+I hope you found this little guide useful, and I bet you are now more than capable of solving a variety of problems using the reflog. You may have noticed that git tries really, _really_ hard not to lose your data and includes [many other](https://www.kernel.org/pub/software/scm/git/docs/git-fsck.html) beautiful utilities to recover lost changes (tools that exceed the scope of this introductory article).
+
+Once you start rebasing and changing history, recovering that seemingly lost data becomes more and more important. A good understanding of the reflog can relieve your headache when things go wrong.
+
+> If you enjoyed this article, let me know! I appreciate all the feedback I can get. You can follow me on twitter at [@jdan](http://twitter.com/jdan)
+<!--stackedit_data:
+eyJoaXN0b3J5IjpbODI2NTUwNzc3XX0=
+--> \ No newline at end of file
diff --git a/hidden/2014-06-22-hello-world.md b/hidden/2014-06-22-hello-world.md
new file mode 100644
index 0000000..097294b
--- /dev/null
+++ b/hidden/2014-06-22-hello-world.md
@@ -0,0 +1,204 @@
+---
+title: Hello, World!
+date: 2014-06-22
+route: /hello-world
+hidden: true
+---
+
+Tinman is a tiny static-ready blog engine based on the
+[toto](http://github.com/cloudhead/toto) library.
+
+```
+npm install -g tinman
+```
+
+## What is Tinman?
+
+A basic tinman blog looks like this:
+
+```
+example
+├── articles
+│   └── 2014-06-22-hello-world.md
+└── public
+ └── style.css
+```
+
+Some features include:
+
+- Generate a simple blog with nothing but a few markdown files
+- Serve anything static by placing it in the _public/_ directory
+- A comprehensive CLI to handle best-practices for you
+- Customize templates to your heart's desire
+- Run your blog as web server or export it as a static site
+- Add any CSS or JavaScript and have it just work
+
+#### What do I get?
+
+Tinman generates a page for each article, and an index page listing your
+articles (sorted by filename). Naming your articles `YYYY-MM-DD-my-title.md`
+will sort them such that the most recent article is listed first. It
+will also set the article's date property for you.
+
+## Usage
+
+#### Create a new blog
+
+```bash
+$ tinman create
+Blog title: myblog
+
+ Your blog is ready! To get started:
+
+ cd myblog/
+ tinman server
+
+$ tinman create myblog
+
+# Generate example templates to play with as well
+$ tinman create myblog --with-templates
+```
+
+#### Generate a new article
+
+```bash
+$ tinman new
+Title: This is my first blog post
+
+ Article generated at: articles/2014-07-05-this-is-my-first-blog-post.md
+```
+
+#### Run your blog on a local webserver
+
+```bash
+$ tinman server
+Server listening on port 3000...
+
+$ tinman server --port 1337
+Server listening on port 1337...
+```
+
+#### Build your blog as a static site
+
+```bash
+$ tinman build
+
+ Blog successfully built to: build/
+
+$ tinman build --output-dir www
+
+ Blog successfully built to: www
+```
+
+## Writing Articles
+
+Articles are written in [Markdown](http://daringfireball.net/projects/markdown/)
+and use YAML Front Matter to set various options.
+
+```markdown
+---
+title: Hello, World!
+date: 2014-06-22
+---
+
+Once upon a time...
+```
+
+This article will be accessible at the url `/hello-world` by default
+(based on the article's title). You can customize this option by either:
+
+- Setting the `slug` property, making the article accessible at
+ `/your-slug-here`
+- Setting the `route` property, and completely overriding the slug (i.e.
+ `route: /2014/06/24/musings/my-article`)
+
+You can include any custom options you'd like (i.e. `color: red`) in
+your YAML Front Matter, and recall it from a custom template.
+
+## Templates
+
+Tinman uses [EJS](http://embeddedjs.com/) templates and includes the
+following:
+
+- **article.ejs** for templating an individual article
+- **index.ejs** for the article index page
+- **layout.ejs** which wraps around the other two and renders asset tags
+
+To customize these templates, pass the `--with-templates` option to
+`tinman create`:
+
+```bash
+$ tinman create myblog --with-templates
+
+ Your blog is ready! To get started:
+
+ cd myblog/
+ tinman server
+
+$ tree myblog
+myblog
+├── articles
+│   └── 2014-06-22-hello-world.md
+├── public
+│   └── style.css
+├── templates
+│   ├── article.ejs
+│   ├── index.ejs
+│   └── layout.ejs
+└── tinman.json
+```
+
+Beyond the articles directory and sample stylesheet, the
+`--with-templates` option creates a templates directory and a
+`tinman.json` file, instructing Tinman to use these templates instead of the
+ones built into it.
+
+#### What Data Do My Templates Receive?
+
+The **article template** receives all properties of the article as defined in
+the YAML Front Matter. The content of the article is stored as `body`. Some
+extra fields include:
+
+- **summary**: the first paragraph of the rendered article
+- **filename**: self-explanatory
+- **date**: the date either extracted from the filename or from the
+ `date` property of the article's Front Matter
+
+The **index template** has access to the array `articles`, which holds
+every article in your blog.
+
+The **layout template** receives the blog's title and a `body`, which
+contains the rendered HTML of the page it contains (either an article
+page or the index). This template also has access to two strings,
+`stylesheets` and `scripts`, which store the CSS/JS resource tags
+generated automatically based on the contents of your public directory.
+
+## Static Files
+
+Tinman will automatically copy static assets (images, stylesheets,
+javascripts) from the "public" directory (default: `public/`).
+
+For instance, you can write the following to **public/css/colors/main.css**:
+
+```css
+body {
+ color: #222222;
+}
+```
+
+And access it like so:
+
+```html
+<link rel="stylesheet" href="/css/colors/main.css" />
+```
+
+You can follow the same pattern for including images in your articles,
+or even serving static HTML documents.
+
+In addition, Tinman scans for javascripts (`*.js`) and stylesheets (`*.css`)
+and automatically generates resource tags which are placed in the layout
+template (_layout.ejs_ is sent both a _scripts_ and _stylesheets_
+string). **You do not need to edit any templates after writing
+javascripts or stylesheets.**
+
+[MIT Licensed](https://github.com/jdan/tinman/blob/master/LICENSE)
diff --git a/hidden/2014-12-30-i-paid-29-dollars-for-this-domain.md b/hidden/2014-12-30-i-paid-29-dollars-for-this-domain.md
new file mode 100644
index 0000000..b87e90d
--- /dev/null
+++ b/hidden/2014-12-30-i-paid-29-dollars-for-this-domain.md
@@ -0,0 +1,22 @@
+---
+title: I Paid $29 For This Domain
+date: 2014-12-30
+route: /i-paid-29-dollars-for-this-domain
+description: The extra push I needed to get back into blogging
+hidden: true
+---
+
+Tonight, I bought a new domain.
+
+I've been meaning to get back into blogging for quite some time. I have lots
+of [silly Medium posts](https://medium.com/friendship-dot-js/) where I flex
+my creative muscles, but very rarely do I write more meaningful things.
+
+I built a [blog engine](http://jdan.github.io/tinman/the-shiniest-blog-engine-in-oz/)
+a few months ago, and here's my chance to use it. It works okay!
+
+But, above all, I needed an extra push to get back into writing. So I went
+ahead and spent some money, got a stupid domain, dusted off some css, and here
+I am writing to you.
+
+I can't wait to see what happens.
diff --git a/hidden/2015-03-17-blogging-is-terrifying.md b/hidden/2015-03-17-blogging-is-terrifying.md
new file mode 100644
index 0000000..cec51f4
--- /dev/null
+++ b/hidden/2015-03-17-blogging-is-terrifying.md
@@ -0,0 +1,25 @@
+---
+title: Blogging is Terrifying
+date: 2015-03-17
+route: /blogging-is-terrifying
+tags: blogging
+hidden: true
+---
+
+I've been meaning to write more things. Not just [any things](https://medium.com/friendship-dot-js), but [meaningful things](https://medium.com/@jdan/my-first-six-weeks-at-khan-academy-19a40bc8136a). So I sat down, fired up my text editor, forked over \$29 for a domain I'll regret in a year, and got to it. But all the preparation in the world can't overcome one thing.
+
+Blogging is terrifying.
+
+Really, it is. It's terrifying to put yourself and your thoughts out there, I mean, what if people actually _read_ this post? What if I make some sort of grammatical mistake; or use a semicolon incorrectly? What if my sentences are too short? What if I say something wrong, and someone comes along and corrects me? Even worse, what if people like it?
+
+What if people like it so much that they read my other posts? What if they like those and share them with their followers? Now even _more_ people will read my stuff, more people to find errors in my writing. What if something I write really strikes a chord with someone, and makes their day better? What if they rely on me to make their day better all the time?
+
+How am I supposed to deal with that kind of pressure?
+
+What if they click through to my projects? What if they like them? What if they use one of those projects at work, and it `rm -rf`'s their system? Code that _I_ wrote, what would happen? What if they read the source code to those projects, and see all the silly formatting errors and awkward variable names I use? What if they learn from it, and use that knowledge in their own projects?
+
+What if they share my projects, and they get noticed by someone important? What if that person organizes a conference and wants me to speak at it? Me, speaking at a conference, I can't do that! What if I do it anyway? What if people think my talk is interesting, and I get asked to speak at more conferences?
+
+What if someone I meet at a conference approaches me to write a book? What if I make a mistake in that book, and a few hundred copies contain that mistake! A few hundred minds spoiled by my missing semicolon, or my misunderstanding about how variable hoisting works. What if they hoist incorrectly at work!? What if the book is good, and they ask me to write a second?
+
+What if it all works out? Then what will I worry about?
diff --git a/hidden/2015-03-17-projects.md b/hidden/2015-03-17-projects.md
new file mode 100644
index 0000000..25aeab4
--- /dev/null
+++ b/hidden/2015-03-17-projects.md
@@ -0,0 +1,28 @@
+---
+title: Projects
+route: /projects
+hidden: true
+timeless: true
+---
+
+**[tota11y](http://khan.github.io/tota11y)**<br>
+tota11y is an "accessibility visualization toolkit" I built and [open-sourced](http://khan.github.io) at [Khan Academy](https://khanacademy.org). It aims to address the some of the frustration and confusion around manual accessibility testing, while providing useful, context-aware error messages and suggestions.
+
+**[isomer](http://jdan.github.io/isomer/)**<br>
+A simple to use isometric graphics library for HTML5 canvas. I built isomer as a fun graphics toy, but I haven't found much use for it in my day-to-day. One day I'll make some art with it.
+
+I'm in the process of revamping isomer to use the popular [three.js](http://threejs.org/) library for WebGL and all the performance benefits that come with it.
+
+**[cleaver](http://jdan.github.io/cleaver)**<br>
+Cleaver generates quick slideshows from familiar Markdown. I built it because I used to procrastinate before giving talks at my school's open-source club.
+
+You can get slides up and running in 30 seconds, and it doesn't look half bad!
+
+**[tinman](http://jdan.github.io/tinman/the-shiniest-blog-engine-in-oz/)**<br>
+Tinman is a blog engine that powers the blog you're reading right now. It is my nth attempt at building a blog engine, and I decided to make it small, simple, and opinionated. You feed it a directory of Markdown posts, and it spits out static HTML. You can customize the templates with JavaScript.
+
+It's easy to fall into the trap of building a static site generator that covers all the use-cases any blogger would want, but I decided to just cover my own use-cases, and hopefully yours match well :)
+
+Tinman was heavily insired by [toto](https://github.com/cloudhead/toto).
+
+[More on GitHub...](https://github.com/jdan)
diff --git a/hidden/2015-03-28-clicking-with-a-keyboard.md b/hidden/2015-03-28-clicking-with-a-keyboard.md
new file mode 100644
index 0000000..eec8d2e
--- /dev/null
+++ b/hidden/2015-03-28-clicking-with-a-keyboard.md
@@ -0,0 +1,231 @@
+---
+title: Clicking with a Keyboard
+date: 2015-03-28
+route: /a11y/clicking-with-a-keyboard
+description: The importance of semantic markup, and getting keyboard accessibility for free. After all, not everyone can use a mouse.
+tags: a11y
+---
+
+Not everyone can use a mouse.
+
+Across the spectrum of disabilities, different types of users rely on different
+mechanisms for navigating around a webpage. In many of these cases a pointing
+device such as a mouse or trackpad is simply not an option, due to either
+insufficient motor skills, or being unable to see.
+
+In fact, a [2003 study of accessible technology potential](http://www.microsoft.com/enable/research/phase1.aspx)
+commissioned by Microsoft found that an estimated _"7% (or 12 million) of
+working-age adults have a severe dexterity difficulty or impairment,"_ one that
+would likely prevent them from using a mouse or trackpad.
+
+It's tough to say how many are attempting use _your_ website, of course, but
+there is also the power user who prefers to stick to their keyboard while
+using your website.
+
+Not everyone _wants_ to use a mouse. Still, the web is full of interactive elements which require a mouse. Why is that?
+
+Let's talk about the most common offender - the `<div>`. Now, the `<div>`
+itself is very useful, but it's vague. It's a broad container of sorts. Not a
+button, not a navbar, not an image - just a big ol' rectangle taking up a few
+rows in our webpage.
+
+Yet, many try to _make_ it one of those things and, rather unfortunately, miss
+out on all the cool things that would happen if we had used the **right tag
+for the job**.
+
+## Let's talk about clicking
+
+Consider the following `<div>` with an `onclick` event handler.
+
+```html
+<div id="action-button">Display</div>
+<span id="output"></span>
+<script>
+ var btn = document.getElementById("action-button")
+ var display = document.getElementById("output")
+
+ btn.onclick = function() {
+ display.innerHTML = "Clicked!"
+ setTimeout(function() {
+ display.innerHTML = ""
+ }, 1000)
+ }
+</script>
+```
+
+<span class="sr-only">
+ Depicted below is a demo of the code above interacting with a mouse
+ pointer. The button works as expected.
+</span>
+<div class="a11y-onclick demo flex" aria-hidden="true">
+ <div class="button">Display</div>
+ <div class="mouse-pointer">
+ <div class="head"></div>
+ <div class="tail"></div>
+ </div>
+ <div class="output">&nbsp;</div>
+</div>
+
+This example does pretty much exactly what you'd expect. We've got a "button"
+and clicking it triggers some action - in this case, putting some text in a
+separate `<div>` before it disappears.
+
+## Keyboard-accessible `<div>`s
+
+But what if we want to add keyboard access to this "button"? First, we'll need
+to understand that users access items with a keyboard through the **tab order**.
+Links (`<a>`) and inputs (`<input>`) will, as you probably already know,
+automatically be navigated to and from using the tab key.
+
+Our fancy `<div>`, on the other hand, can't - a `<div>` (and many other tags
+like `<p>`, `<b>`, etc) will not find themselves in the tab order, so we'll
+have to put in some extra work. We'll start by specifying `tabindex="0"` on
+the tag.
+
+In the following example we've tabbed to the button and focused it (as shown
+by the black outline), and now we'll attempt to activate the button using the
+enter key (just as we would on a hyperlink or form).
+
+```html
+<div id="action-button" tabindex="0">Display</div>
+```
+
+<span class="sr-only">
+ Depicted below is the same demo as before, this time interacting with
+ the enter key. The button does not seem to do anything.
+</span>
+<div class="a11y-onclick demo no-active no-output" aria-hidden="true">
+ <div class="row">
+ <div class="enter-key">enter</div>
+ </div>
+ <div class="flex">
+ <div class="button focused">Display</div>
+ <div class="output">&nbsp;</div>
+ </div>
+</div>
+
+Unfortunately, nothing happens :( Our "button" is listening for a click event,
+but we're attempting to activate it with a keyboard.
+
+Let's try and fix this by adding a `keypress` event handler.
+
+```html
+<div id="action-button" tabindex="0">Display</div>
+<span id="output"></span>
+<script>
+ var btn = document.getElementById("action-button")
+ var display = document.getElementById("output")
+
+ var activate = function() {
+ display.innerHTML = "Clicked!"
+ setTimeout(function() {
+ display.innerHTML = ""
+ }, 1000)
+ }
+
+ btn.onclick = activate
+ btn.onkeypress = function(e) {
+ // enter key
+ if (e.keyCode === 13) {
+ activate()
+ }
+ }
+</script>
+```
+
+<span class="sr-only">
+ Depicted below is a demo of the code above interacting with the enter key.
+ The button now works similarly to the mouse pointer example before.
+</span>
+<div class="a11y-onclick demo no-active" aria-hidden="true">
+ <div class="row">
+ <div class="enter-key">enter</div>
+ </div>
+ <div class="flex">
+ <div class="button focused">Display</div>
+ <div class="output">&nbsp;</div>
+ </div>
+</div>
+
+I'll call that a success, sort of! The code's a bit longer now (even after
+a bit of refactoring), but it definitely works - our "button" can be
+activated with both a mouse and a keyboard, just like a real button.
+
+But hold on a second, why don't we just _use_ a real button?
+
+## Semantic buttons
+
+As I briefly mentioned earlier, there's nothing special about a `<div>`. It
+doesn't have any magical behavior, it's just a container. We can shape it,
+paint it, and make it _look_ like a button, but it's not a button.
+
+We can write some extra JavaScript to make it _act_ like a button, but it's
+**not a button**.
+
+In fact, we're doing all this extra work (and often skipping it), when in
+reality we could just a real button, the semantic `<button>` tag. Let's try
+it.
+
+```html
+<button id="action-button">Display</button>
+<span id="output"></span>
+<script>
+ var btn = document.getElementById("action-button")
+ var display = document.getElementById("output")
+
+ btn.onclick = function() {
+ display.innerHTML = "Clicked!"
+ setTimeout(function() {
+ display.innerHTML = ""
+ }, 1000)
+ }
+</script>
+```
+
+<span class="sr-only">
+ Depicted below is a demo of the code above interacting with both a mouse
+ pointer and the enter key. Both of these interactions work.
+</span>
+<div class="a11y-onclick demo flex" aria-hidden="true">
+ <div class="button">Display</div>
+ <div class="mouse-pointer">
+ <div class="head"></div>
+ <div class="tail"></div>
+ </div>
+ <div class="output">&nbsp;</div>
+</div>
+
+<div class="a11y-onclick demo no-active" aria-hidden="true" aria-label="an example with a div and an onkeypress event">
+ <div class="row">
+ <div class="enter-key">enter</div>
+ </div>
+ <div class="flex">
+ <div class="button focused">Display</div>
+ <div class="output">&nbsp;</div>
+ </div>
+</div>
+
+Excellent! Now that we are using the semantic `<button>` tag, **we get a click
+event for free**. No need to track both a `click` and `keypress` handler here,
+just a `click` will do.
+
+**It turns out that in order to make our button accessible, we don't have to
+do much.** In fact, the _only_ difference between our first example and this
+one is that we've replaced the `<div>` with a `<button>` - seriously!
+
+## Sane markup goes a long way
+
+Beyond the obvious markup clarity and keyboard accessibility gains, we also
+unlock a few bonus features when we use a `<button>` instead of a `<div>`. For
+instance, a copy of VoiceOver on OSX will display the following:
+
+![VoiceOver for Mac describing the button as "Display, button"](/img/voiceover.png)
+
+Letting users with screen readers know that "Display" corresponds to a button
+that can be activated with either their enter key or space bar.
+
+So go forth and prevent your contemporaries from skipping the semantic markup,
+make sure your buttons play nicely with the keyboard, and stop trying to copy
+browser behavior provided to you **for free**.
+
+It doesn't take much, but it makes a big difference.
diff --git a/hidden/2015-03-30-focus-and-hover-hand-in-hand.md b/hidden/2015-03-30-focus-and-hover-hand-in-hand.md
new file mode 100644
index 0000000..38006d0
--- /dev/null
+++ b/hidden/2015-03-30-focus-and-hover-hand-in-hand.md
@@ -0,0 +1,125 @@
+---
+title: Focus vs. Hover
+route: /a11y/focus-vs-hover
+date: 2015-04-03
+description: Visual interactions should never require a mouse, but a common CSS pattern leaves many keyboard-wielding visitors in the dark.
+tags: a11y
+---
+
+Visual interactions should never require a mouse.
+
+Consider the ever-popular `:hover` selector, which is used to declare styles
+on elements under a mouse cursor.
+
+```css
+.nav-link {
+ color: #ffffff;
+}
+
+.nav-link:hover {
+ opacity: 0.8;
+}
+```
+
+<span class="sr-only">
+ The following example shows a mouse cursor hovering over various items
+ in a navigation bar which highlight as the mouse passes over them.
+</span>
+<div class="demo hover-a11y" aria-hidden="true">
+ <div class="row flex">
+ <div class="mouse-pointer">
+ <div class="head"></div>
+ <div class="tail"></div>
+ </div>
+ <div class="button">Home</div>
+ <div class="button">About</div>
+ <div class="button">Products</div>
+ </div>
+</div>
+
+If we attempt to access these buttons with a keyboard, however, we're met
+with an unfortunate short-coming.
+
+Nothing happens.
+
+<span class="sr-only">
+ The following example is just a navigation bar with three links and no
+ animation, because we did not declare our styles correctly.
+</span>
+<div class="demo hover-a11y no-active" aria-hidden="true">
+ <div class="row flex">
+ <div class="button">Home</div>
+ <div class="button">About</div>
+ <div class="button">Products</div>
+ </div>
+</div>
+
+Instead, we need to use the `:focus` selector, which is used to declare styles
+on focused elements - the items on the page that keyboards can highlight with
+subsequent presses of the tab key.
+
+**All we need to do is add the `:focus` selector**, so that it is applied in
+addition to the hover styles.
+
+```css
+.nav-link:hover,
+.nav-link:focus {
+ opacity: 0.8;
+}
+```
+
+Ta-da, now we get the same effect even when we're using a keyboard instead of
+a mouse! **This helps users keep track of their location in their page**, and
+also presents the same information that a mouse-wielding user would receive.
+
+<span class="sr-only">
+ The example below shows three links on a nav bar highlighting in sequence
+ as a theoretical keyboard tabs to each of them.
+</span>
+<div class="demo hover-a11y" aria-hidden="true">
+ <div class="row flex">
+ <div class="button">Home</div>
+ <div class="button">About</div>
+ <div class="button">Products</div>
+ </div>
+</div>
+
+We can do still do better, though. Many of your visitors will be unable to
+detect subtle color changes (and, in some cases, very obvious ones!), so we'll
+add an underline to the link.
+
+It's okay if you don't want this on hover - since a mouse cursor is a good
+visual indicator in and of itself - but it's pretty essential to do this on
+focus for visitors using the keyboard.
+
+```css
+.nav-link:hover,
+.nav-link:focus {
+ opacity: 0.8;
+}
+
+.nav-link:focus {
+ text-decoration: underline;
+}
+```
+
+<span class="sr-only">
+ The following example is the same as the previous example, but the links
+ also have an underline when focused.
+</span>
+<div class="demo hover-a11y with-underline" aria-hidden="true">
+ <div class="row flex">
+ <div class="button">Home</div>
+ <div class="button">About</div>
+ <div class="button">Products</div>
+ </div>
+</div>
+
+**Side note**: While browsers like Chrome _will_ declare a
+`focus-ring-color` outline for focused elements by default (a glowing blue
+outline), it's far too easy to accidentally remove these - and sadly many do!
+
+Adding a `:focus` rule is a trivial change with a potentially great impact, so
+there's no real reason not make use of it. Always keep keyboard accessibility
+in mind when writing style rules for interactions, and try not to leave any of
+your users in the dark.
diff --git a/hidden/2015-04-26-the-imperfect-development-setup.md b/hidden/2015-04-26-the-imperfect-development-setup.md
new file mode 100644
index 0000000..2da8f6f
--- /dev/null
+++ b/hidden/2015-04-26-the-imperfect-development-setup.md
@@ -0,0 +1,8 @@
+---
+title: The Imperfect Development Setup
+slug: the-imperfect-development-setup
+---
+
+I've put probably many days into my dev setup - the perfect font, the perfect
+color scheme, the perfect shortcut. And when I'm feeling unproductive, I think
+of the hours I've spent. \ No newline at end of file
diff --git a/hidden/2015-07-28-building-jquery-elements-with-jsx.md b/hidden/2015-07-28-building-jquery-elements-with-jsx.md
new file mode 100644
index 0000000..de72e1b
--- /dev/null
+++ b/hidden/2015-07-28-building-jquery-elements-with-jsx.md
@@ -0,0 +1,38 @@
+---
+title: Building jQuery Elements with JSX
+date: 2015-07-28
+route: /jquery-and-jsx
+hidden: true
+---
+
+I've been spending a good amount of time with [tota11y](https://khan.github.io/tota11y), an accessibility visualization tool we shipped last month at Khan Academy. Specifically, spending time doing code cleanup to make it easier for contributors to wrap their head around how the tool works.
+
+tota11y is built with webpack, and written in ES6 using the wonderful Babel library. Its UI is mostly powered by jQuery. Now you're almost up to speed - that wasn't so bad, was it?
+
+Well, my jQuery chops are not very good, and tota11y rather quickly devolved into jQuery soup. tota11y is modular, meaning that each "plugin" lives in its own directory that encapsulates its logic, templates, and styling. It's clean and provides a lot of flexibility, but the boundaries between these modules is where things get a little suspicious.
+
+##
+
+```js
+let $toolbar = $(toolbarTemplate())
+$("body").append($toolbar)
+
+$toolbar.find(".tota11y-toolbar-toggle").click(e => {
+ e.preventDefault()
+ e.stopPropagation()
+ $toolbar.toggleClass("tota11y-expanded")
+})
+```
+
+Where `toolbarTemplate` corresponds to the following handlebars template.
+
+```html
+<div class="tota11y tota11y-toolbar">
+ <div class="tota11y-toolbar-body">
+ <div class="tota11y-plugins"></div>
+ </div>
+ <a href="#" class="tota11y-toolbar-toggle">
+ Open
+ </a>
+</div>
+```
diff --git a/hidden/2015-08-10-reenergizing-my-love-for-web-development.md b/hidden/2015-08-10-reenergizing-my-love-for-web-development.md
new file mode 100644
index 0000000..1c43eb2
--- /dev/null
+++ b/hidden/2015-08-10-reenergizing-my-love-for-web-development.md
@@ -0,0 +1,92 @@
+---
+title: Falling in Love with Web Development Again
+route: /loving-web-development-again
+description: '"I''ve had a growing interest in accessibility lately. Screen readers, Braille keyboards, that sort of thing," I said, offhand.'
+date: 2015-08-10
+tags: a11y
+---
+
+During one of our weekly 1-on-1's, my manager [Marcia](https://twitter.com/marcia_lee) and I were speaking casually about some of the things I was interested in pursuing. I was a few months into my new job at [Khan Academy](https://khanacademy.org), building tools for teachers and parents.
+
+"I've had a growing interest in accessibility lately. Screen readers, Braille keyboards, that sort of thing," I said, offhand.
+
+My pal [Cassidy](https://twitter.com/cassidoo) had recently given a talk on accessibility, and I had been poking around Bootstrap's source code - seeing various mentions of this "aria" thing. It all seemed pretty nifty, but admittedly I knew _nothing_ about it, just that it helped people use the software I was writing.
+
+"Oh cool, I'll keep that in mind," replied Marcia (it was more enthusiastic than I can convey in a blog post). The rest of the walk was pretty normal - I think we saw some chickens that day.
+
+---
+
+Prior to starting my career, I spent a lot of my free time in college [building various things](/projects). I learned a lot this way - gaining some technical know-how, and even more non-technical skills like communication and time management.
+
+As I got into the groove of things at work, time passed, and my interests wandered. Issues built up, discussion died down, and I became less interested in the problems my open source projects were solving.
+
+**Because at the end of a long day of coding, writing more code isn't the most appealing thing in the world, and that's okay**. The unfortunate side-effect is that the way I used to learn technology - reckless experimentation - no longer held a significant presence in my day-to-day.
+
+Work was going well - really well in fact. I had the opportunity to <a href="http://facebook.github.io/react/" aria-label="use cool technologies like React">use cool technologies</a> and solve interesting problems, but I wanted to make sure I still had opportunities to learn something _completely_ new. At work I had to follow style guidelines and deal with deadlines, while in college I'd spend three hours reading a rails tutorial and writing a blog engine I would never use.
+
+So, I was in a bit of a funk. I found myself reluctant to work on side projects, and was worried I wouldn't be leaving my comfort zone enough at work. I was hungry to attack something I had _zero_ experience with, just like I did in between classes in college.
+
+---
+
+An email found its way into my inbox about six weeks later.
+
+![Email. Subject: Invitation: Chat about accessibility @ Fri Oct 24, 2014 11:15am - 11:30am (Jordan Scales)](/img/a11y-invite.png)
+
+Word had spread that I was interested in accessibility (thanks Marcia), and an opportunity came up to do some related work as part of a new series of projects at Khan Academy. I happily accepted, and started doing some research.
+
+I found a [great talk on accessibility testing](https://www.youtube.com/watch?v=rxh6B3ChLIc) by Google's [Alice Boxhall](https://twitter.com/sundress). In it, she mentions [Accessibility Developer Tools](https://github.com/GoogleChrome/accessibility-developer-tools): a suite of tools for accessibility testing. "Neat! I know some of these words," I thought, and added the link to my in-progress "a11y-resources.txt".
+
+So I played around with [OSX's built-in screen reader](https://www.apple.com/voiceover/info/guide/), learned how folks with impaired vision or motor skills relied on the keyboard, and read up on some common accessibility mistakes.
+
+I started making blanket statements such as "These things can be completed in about a day" and "These things may take a week or more to fix." They were broad guesses, I had no idea what I was doing, and I was loving every minute of it. **Here I was, once again feeling my way around a new world of software development.** One day I'd think I knew something and the next I'd learn I was wrong.
+
+But I pushed forward and completed a write-up (my a11y puns have gotten way better since).
+
+![Google doc: Accessicademy: on improving the accessibility (a11y) efforts at Khan Academy (k11y)](/img/accessicademy.png)
+
+Fun fact, not only was I terrible at accessibility, but I was also terrible at numeronyms - "Khan Academy" has _nine_ letters between the K and Y (or 10 if you count the space).
+
+---
+
+Thus began a few weeks of accessibility fixes: trying to fix things, not fixing them correctly, then learning and re-fixing. Each day my colleague [John](http://ejohn.org) and I would share links in our "Accessibility" HipChat room, and attempt to answer each other's questions.
+
+I realized that this sliver of software development was _far_ larger than I originally thought, and it was full of well-thought-out standards as well as many unknowns. Lots of work to be done, and I was excited to dive in head-first. I started <a href="/a11y/clicking-with-a-keyboard/" aria-label="blogging: Clicking with a Keyboard - thatjdanisso.cool">blogging</a> <a href="/a11y/focus-vs-hover/" aria-label="again: Focus vs. Hover - thatjdanisso.cool">again</a>, and became overwhelmingly excited to learn and share.
+
+We built some testing infrastructure using [Accessibility Developer Tools](https://github.com/GoogleChrome/accessibility-developer-tools), caught some bugs, caught some things that _seemed_ like bugs, and started off on the monumental task of evangelizing web accessibility at Khan Academy.
+
+There were scary moments, of course. _What if I'm doing this wrong? What if I'm spreading misinformation? What if I'm making the experience even worse for people?_ But I kept an open mind, accepted the fact that mistakes would be made, and embraced my potential to figure it out.
+
+But there was one issue that stayed in the back of my head - I felt a _disconnect_ between my code and the issues it was causing. Other devs felt it too. I could preach all day about using the right tags with the right attributes, but how could I make these things more obvious? **How could I make accessibility development an interesting and informative experience?**
+
+---
+
+A few months later, during one of "Web Frontend" team meetings (don't worry, we don't quite know what the distinction is, either), I pitched this very idea - an interactive accessibility testing utility. I was still new to accessibility, everyone knew it, but folks agreed it seemed like an interesting idea. I didn't know what it would look like, I didn't know exactly what it would do, but I wanted to do it.
+
+So I did it.
+
+[![A sunglasses logo with the text "tota11y" beneath it](/img/tota11y.png)](http://khan.github.io/tota11y)
+
+At the end of the month we released [tota11y](http://khan.github.io/tota11y) - an "accessibility visualization toolkit" - to the world, accompanied by [a post on our engineering blog](http://engineering.khanacademy.org/posts/tota11y.htm).
+
+The response was minimal, a few bites on social media at first, but people seemed to like it.
+
+<blockquote class="twitter-tweet" lang="en" width="590"><p lang="en" dir="ltr">Super excited to announce <a href="https://twitter.com/tota11y">@tota11y</a> – a visualization toolkit to help reduce the friction of accessibility testing <a href="http://t.co/b8B4uxHq8x">http://t.co/b8B4uxHq8x</a></p>&mdash; Jordan Scales (@jdan) <a href="https://twitter.com/jdan/status/607923883639980032">June 8, 2015</a></blockquote>
+<script async src="//platform.twitter.com/widgets.js" charset="utf-8"></script>
+
+Interestingly enough, tota11y was posted again (by someone else) to Hacker News a few weeks later, making its way to the top on a Monday morning and staying there until late in the evening.
+
+It was incredibly energizing to see so many folks talking about accessibility, many of whom were in the same boat as I was - web developers who knew it was a _thing_ but weren't sure how to approach the problem. But among those web developers were many who knew _a lot_ about accessibility. They spread the word, offered advice, and provided tons of motivating feedback.
+
+I learned a lot more from them as I went along, the most important lesson being that **everyone in this space is _so friggin' nice_. Like, seriously, _so nice_.** A bunch of approachable folks, eager to answer any question you throw at them. In my (admittedly short few) years in software, I've never come across a community as kind and welcoming.
+
+And it makes sense, given the work that they do, but it's just such a pleasant surprise.
+
+---
+
+**If you're looking for some cool things to learn, I highly encourage you to experience the wild world of accessibility and its amazing community.** There's a lot of room for improvement on the web, and chances are that your skills can impact many, many people. The barrier of entry is low, the impact is high, and you can [start learning wherever you'd like](http://a11yproject.com/).
+
+I get an incredible sense of satisfaction doing this work - whether it's running a screen reader on a new interface, adding a new feature to tota11y, or just interacting with folks in [the web-a11y slack channel](https://twitter.com/ryanflorence/status/578240236267773952). Each day brings a new challenge, and I feel empowered (and _excited_) to tackle it.
+
+Looking back on the past twelve months, I realize how quickly new interests can come forward and how, with time and care, you can learn a lot about new things. I'm still brand new to this field, and there's _so much_ more to learn, but I can't even begin to describe how excited I am for whatever's next.
+
+I've fallen in love with web development all over again.
diff --git a/hidden/2015-09-07-removing-collaborator-spam-from-your-github-feed.md b/hidden/2015-09-07-removing-collaborator-spam-from-your-github-feed.md
new file mode 100644
index 0000000..5ab5aef
--- /dev/null
+++ b/hidden/2015-09-07-removing-collaborator-spam-from-your-github-feed.md
@@ -0,0 +1,79 @@
+---
+title: Removing "Collaborator Spam" from Your GitHub Feed
+route: /github-collaborator-spam
+date: 2015-09-07
+description: Devious GitHub users are adding prominent members as collaborators to their repositories in an attempt to gain exposure. Here's how to stop it.
+hidden: true
+---
+
+I'm sick of seeing this garbage on GitHub.
+
+![A series of items on the GitHub activity feed in the form of "X added Y to Z"](/img/github-spam.png)
+
+Project owners shamelessly adding dozens upon dozens of famous open source developers, in an attempt to get a link to their project on as many GitHub feeds as possible (while [chalking it up to "username errors"](https://github.com/joni2back/angular-filemanager/issues/59)). To be honest, I don't really care what repositories my friends get added to - even if legitimately. The feature has never proven useful to me.
+
+**Here's how to get rid of it.**
+
+## Using [AdBlock Plus](https://adblockplus.org/)
+
+We can remove the spammy elements from the page using a custom AdBlock filter. First, open up your extension settings (I'm using Chrome):
+
+![Chrome's extension settings page, showing the options for the AdBlock Plus extension](/img/extension-settings.png)
+
+Then, click the "Add your own filters" tab, enter the following filter string, and click "Add Filter":
+
+```
+github.com##.alert.member_add
+```
+
+![The "Add your own filters" tab of AdBlock Plus's extension settings, showing the string "github.com##.alert.member_add" entered in an input box.](/img/custom-filters.png)
+
+This filter is made up of three parts:
+
+- `github.com` - the domain on which to filter
+- `##` - a separator
+- `.alert.member_add` - the CSS selector, matching the various `<div class="alert member_add simple">` elements containing the spam
+
+Refresh your GitHub tab, and rejoice in your spam-free activity feed.
+
+## Using a custom userscript
+
+Alternatively, you can use the following userscript.
+
+```js
+// ==UserScript==
+// @name Remove collab spam
+// @description Removes "X added Y to Z" spam from the GitHub activity feed
+// @include https://github.com/*
+// ==/UserScript==
+//
+// by @jdan <http://thatjdanisso.cool>
+// MIT Licensed
+
+;[].slice
+ .call(document.querySelectorAll(".alert.member_add"))
+ .forEach(function(item) {
+ item.remove()
+ })
+```
+
+Copy and paste the above code into your favorite editor and save it as **remove-collab-spam.user.js**. In Chrome, you can simply drag this file into **chrome://extensions**.
+
+On Firefox, install [Greasemonkey](https://addons.mozilla.org/en-US/firefox/addon/greasemonkey/) and navigate to the Greasemonkey "User scripts" tab of **about:addons**.
+
+![Firefox's about:addons page, showing a selected "User scripts" tab on the left, with a drag-and-drop area on the right](/img/greasemonkey.png)
+
+Drag the file here, then accept the following confirmation dialog.
+
+![A confirmation dialog from Greasemonkey for installing the userscript. The confirmation includes the title and description of the userscript](/img/greasemonkey-confirm.png)
+
+You can even customize the userscript to remove/keep elements containing particular usernames if you're into that sort of thing.
+
+```js
+// Always show tj-related activity items
+if (!/\/tj/.test(item.innerHTML)) {
+ item.remove()
+}
+```
+
+But I think we can safely remove 'em all :)
diff --git a/hidden/2015-10-15-ask-me-anything.md b/hidden/2015-10-15-ask-me-anything.md
new file mode 100644
index 0000000..786ca5e
--- /dev/null
+++ b/hidden/2015-10-15-ask-me-anything.md
@@ -0,0 +1,16 @@
+---
+title: Ask Me Anything!
+route: /ask-me-anything
+date: 2015-10-15
+hidden: true
+---
+
+Inspired by [Sindre Sorhus](https://github.com/sindresorhus/ama), I've decided
+to make an "AMA" repo on GitHub. I don't know if anyone will use it, but I'll
+leave it there anyway!
+
+So, [ask me anything](https://github.com/jdan/ama) about Khan Academy,
+accessibility, JavaScript, being a junior developer - anything that's on your
+mind.
+
+![screenshot of my AMA repo on GitHub, with horrible wordart and all](/img/ama.png)
diff --git a/hidden/2016-04-24-random-bowling.md b/hidden/2016-04-24-random-bowling.md
new file mode 100644
index 0000000..9186b17
--- /dev/null
+++ b/hidden/2016-04-24-random-bowling.md
@@ -0,0 +1,30 @@
+---
+title: Let's Go Bowling
+route: /toyshop/bowling
+date: 2016-04-24
+description: An experiment in scoring random bowling games.
+---
+
+<link rel="stylesheet" type="text/css" href="/css/bowling.css">
+
+<div id="demo">
+ <div id="graph"></div>
+ <div id="stats"></div>
+ <div id="scoreboard"></div>
+ <div id="controls">
+ <button id="throw">Generate throw</button>
+ <button id="game">Generate game</button>
+ <button id="many-games">Generate 100 games</button>
+ </div>
+ <script src="/js/bowling.js"></script>
+</div>
+
+Here's a little experiment in scoring random bowling games. Each throw
+consists of a random number between 0 and the number of pins remaining. This
+is not even remotely close to modeling the actual physical aspect of knocking
+pins down, but nevertheless I was curious how a point distribution may look.
+
+A side goal of this experiment was to be more pragmatic in my coding. I've
+written bowling scoring logic dozens of times, each an attempt at making the
+code clearer and more readable. I decided to throw all of that out the window
+and just get it done(TM).
diff --git a/hidden/2016-05-27-color-blindness.md b/hidden/2016-05-27-color-blindness.md
new file mode 100644
index 0000000..8ef5aa7
--- /dev/null
+++ b/hidden/2016-05-27-color-blindness.md
@@ -0,0 +1,56 @@
+---
+title: Color Blindness Simulator
+route: /toyshop/color-blindness
+description: An experiment with color blindness simulation.
+date: 2016-05-27
+hidden: true
+---
+
+Here's an experiment with using SVG Filters to simulate color blindness. Props
+to [Kyo Nagashima](https://github.com/hail2u) for [the inspiration](https://github.com/hail2u/color-blindness-emulation).
+
+<link rel="stylesheet" type="text/css" href="/css/color-blindness.css">
+
+<div class="demo-content">
+ <div id="controls">
+ <div class="control">
+ <label for="red">
+ <span class="label-text">Red</span>
+ <span class="label-value" id="red-value">(100)</span>
+ </label>
+ <input id="red" type="range" min="0" max="100" value="100" step="5">
+ </div>
+ <div class="control">
+ <label for="blue">
+ <span class="label-text">Blue</span>
+ <span class="label-value" id="blue-value">(100)</span>
+ </label>
+ <input id="blue" type="range" min="0" max="100" value="100" step="5">
+ </div>
+ <div class="control">
+ <label for="green">
+ <span class="label-text">Green</span>
+ <span class="label-value" id="green-value">(100)</span>
+ </label>
+ <input id="green" type="range" min="0" max="100" value="100" step="5">
+ </div>
+ </div>
+ <img src="https://color.adobe.com/build2.0.0-buildNo/resource/img/kuler/color_wheel_730.png" />
+ <img src="https://upload.wikimedia.org/wikipedia/commons/e/e0/Ishihara_9.png" />
+ <svg>
+ <defs>
+ <filter id="color-filter">
+ <feColorMatrix
+ id="color-matrix"
+ in="SourceGraphic"
+ type="matrix"
+ values="1, 0, 0, 0, 0
+ 0, 1, 0, 0, 0
+ 0, 0, 1, 0, 0
+ 0, 0, 0, 1, 0" />
+ </filter>
+ </defs>
+ </svg>
+</div>
+
+<script src="/js/color-blindness.js"></script>
diff --git a/hidden/2017-08-15-ocaml-microservices.md b/hidden/2017-08-15-ocaml-microservices.md
new file mode 100644
index 0000000..36ed4b2
--- /dev/null
+++ b/hidden/2017-08-15-ocaml-microservices.md
@@ -0,0 +1,293 @@
+---
+title: How to build disruptive OCaml microservices with BuckleScript
+route: /js-ocaml-microservices
+date: 2017-08-15
+description: For a few hours this past week, I decided to cram a bunch of different Very Fun™ things together to build a trivial web app.
+---
+
+Recently, I started tinkering with [OCaml](https://ocaml.org/). It’s Very Fun™,
+which makes it a great fit for a quick side project. So, for a few hours this
+past week, I decided to cram a bunch of different Very Fun™ things together to
+build a trivial web app. It went well — so here’s a tutorial on how I did it.
+
+---
+
+### The Plan
+
+We’ll be writing a few lines of [OCaml](https://ocaml.org/), compiling it to
+JavaScript using [BuckleScript](https://github.com/bucklescript/bucklescript),
+producing a .js file which runs a microservice using
+[Micro](https://github.com/zeit/micro).
+
+“Fatigue!” you may claim. Yeah, that’s kinda the point. We’ll get our hands
+dirty with a variety of cool things, each of which can be further explored or
+ignored as you see fit — that was my plan, anyway.
+
+### The Tools
+
+We’ll need a couple things from npm:
+
+ yarn add micro # or replace 'yarn' with 'npm install'
+ yarn add --dev bs-platform
+
+Allow me to briefly explain what these two things are.
+
+---
+
+[Micro](https://github.com/zeit/micro) is a super-tiny library for turning
+blocks of code like this…
+
+ module.exports = (req, res) => {
+ res.end('Welcome to Micro')
+ }
+
+…into web-servers. Just a few lines, no need for any boilerplate or
+configuration, making our lives much easier as we attempt to compile another
+language into it.
+
+[BuckleScript](https://github.com/bucklescript/bucklescript) is a toolchain
+developed at Bloomberg for compiling OCaml code into readable, performant
+JavaScript. It is incredibly powerful, and we’ll only be using a very, *very
+*small subset of its features, but it works quite well and is easy to get up and
+running.
+
+---
+
+Now a bit of config.
+
+First we’ll need to tell BuckleScript where our files are (let’s make a new
+`src/` directory and just put things in there). To do this we create a
+`bsconfig.json` with the following two fields.
+
+ {
+ "name": "bucklescript-micro-example",
+ "sources": [
+ "src/"
+ ]
+ }
+
+Sa-weet — now let’s add some scripts to `package.json` to make our lives easier.
+
+ {
+ "dependencies": {
+ "micro": "^8.0.1"
+ },
+ "devDependencies": {
+ "bs-platform": "^1.8.2"
+ },
+ "scripts": {
+ "build": "bsb",
+ "watch": "bsb -w",
+ "start": "node lib/js/src/index.js"
+ }
+ }
+
+Our `build` command will use the `bsb` executable (provided to us from
+`bs-platform`) to build our code. `watch` does the same thing, but will also
+watch for any file changes as we develop and re-build them automagically.
+
+Finally, `start` will run our web server. The path afterwards is where
+BuckleScript will put our compiled JavaScript.
+
+### The Code
+
+So far so good, right? Now, we can start writing code. Let’s kick things off
+with a simple function, just to get a feel for how BuckleScript works its magic.
+Start by creating a file `src/add.ml` and add the following:
+
+ let add a b = a + b
+
+If we run `npm run build` (or we can run `npm run watch` and leave it in a
+separate tab), we should see a brand new `lib/` folder in our profile. Diving
+in, we find lots of definitions, and the compiled output: `lib/src/add.js`:
+
+ // Generated by BUCKLESCRIPT VERSION 1.8.2, PLEASE EDIT WITH CARE
+ 'use strict';
+
+ function add(a, b) {
+ return a + b | 0;
+ }
+
+ exports.add = add;
+ /* No side effect */
+
+Magic! Not only did we compile the code, but we’re exporting our `add` function
+(with the right name and all). We can now use our `add` function, originally
+written in OCaml, in node:
+
+ $ node
+ > require("./lib/js/src/add.js").add(5, 6)
+ 11
+
+Now the fun part — let’s try to write some code that uses
+[micro](https://github.com/zeit/micro).
+
+### The Bindings
+
+We can write functions like `add` ourselves, but in order to interface our code
+with existing JS functions, we’ll need to dive into the world of
+[foreign function interface](https://en.wikipedia.org/wiki/Foreign_function_interface)_s
+_(also known as FFIs).
+
+Simply put, FFIs let BuckleScript know:
+
+- **The type definitions of our foreign objects**. This allows us to treat these
+ objects as first class citizens, passing them to and from other functions in
+ our codebase. (For example, `micro` will provide us with “request” and
+ “response” objects. We can type these so later on we can write functions such
+ as `renderIndexPage : res -> string -> unit`).
+- **What type of syntax our OCaml code should compile down to. **In other words,
+ should `fillStyle ctx "blue"` compile down to `ctx.fillStyle("blue")` or
+ `ctx.fillStyle = "blue"`?
+
+These bullet points will make more sense as we go along. For now, let me
+introduce what one of these bindings looks like.
+
+ type req
+ type res
+ type server
+ external micro : (req -> res -> string) -> server = "micro" [@@bs.module]
+ external listen : server -> int -> unit = "listen" [@@bs.send]
+
+Let’s break this down.
+
+- First we define a few types. Now we can create functions that consume/return a
+ “thing” of type `req`, `res`, and `server`.
+- `external` is a keyword used for defining FFIs in OCaml. You’ll see this a lot
+ when working with BuckleScript
+- `micro` and `listen` will correspond to functions we can now use in our OCaml
+ code. Thanks to the type definitions next to them (after the colon), they are
+ typesafe and will let your program compile (as well as make tooling such as
+ [merlin](https://github.com/ocaml/merlin) infinitely more useful).
+- The strings `"micro"` and `"listen"`, somewhat confusingly, correspond to the
+ JavaScript identifiers that BuckleScript will output. We can technically leave
+ these out (and instead specify `""`) since they are equal to the function
+ names we are binding to.
+- Finally, the items in the square brackets (namely `bs.module` and `bs.send`)
+ let BuckleScript know what sort of JavaScript expression we want our new
+ `micro` and `listen` functions to compile to.
+
+---
+
+I’d like to expand that last bullet point.
+
+#### `[@@bs.send]`
+
+This treats the first argument as a JS object and sends the remaining arguments
+as parameters.
+
+ external listen : server -> int -> unit = "listen" [@@bs.send]
+ (* ...other stuff... *)
+ listen thing_of_type_server 1337
+
+Will result in (roughly) the following code:
+
+ thing_of_type_server.listen(1337)
+
+#### [@@bs.module]
+
+This attribute lets BuckleScript know that you are interfacing with a JS module,
+adding a `require` when necessary.
+
+ external add : int -> int -> int = "add" [@@bs.module]
+ external sub : int -> int -> int = "sub" [@@bs.module "coolpackage"]
+ let f = add 1 2;;
+ let g = sub 7 6;;
+
+Results in:
+
+ var Add = require("add");
+ var Coolpackage = require("coolpackage");
+
+ var f = Add(1, 2);
+ var g = Coolpackage.sub(7, 6);
+
+**As an exercise to the reader: what do the attributes**`[@@bs.get]`**,
+**`[@@bs.set]`**, and **`[@@bs.val]`** do?**
+
+For more on FFI: refer to the
+[official BuckleScript docs](https://bucklescript.github.io/bucklescript/Manual.html#_ffi).
+
+---
+
+We now have access to two functions: `micro` and `listen` which are used in the
+following ways:
+
+`micro` accepts a function (which accepts two arguments of type `req` and `res`
+respectively and returns a `string`) and returns a `server`.
+
+ let server = micro (fun req -> fun res -> "Hello, world!");;
+
+`listen` accepts a `server` and an `int` and returns a noop (type `unit`).
+
+ listen server 1337;;
+
+All together now!
+
+ type req
+ type res
+ type server
+ external micro : (req -> res -> string) -> server = "micro" [@@bs.module]
+ external listen : server -> int -> unit = "listen" [@@bs.send]
+
+ let server = micro (fun req -> fun res -> "Hello, world!");;
+ listen server 1337;;
+
+If we place this code in `src/index.ml`, running `npm run build` will produce
+`lib/js/src/index.js` with the following contents:
+
+ // Generated by BUCKLESCRIPT VERSION 1.8.2, PLEASE EDIT WITH CARE
+ 'use strict';
+
+ var Micro = require("micro");
+
+ var server = Micro((function (_, _$1) {
+ return "Hello, world!";
+ }));
+
+ server.listen(1337);
+
+ exports.server = server;
+ /* server Not a pure module */
+
+Now let’s run `npm start` and visit `localhost:1337` .
+
+![A screenshot of a web browser showing a document with the text "Hello, world!"](https://cdn-images-1.medium.com/max/1600/1*MNeQbgDiAklOzLuQMwlrIA.png)
+
+Better yet, we can install [now](https://github.com/zeit/now) (`npm install -g now`) and deploy our site instantly (simply by typing `now` in our terminal).
+
+![A screenshot of a web browser showing a document with the text "Hello, world!"](https://cdn-images-1.medium.com/max/1600/1*jIDDvhlUevt-3yRUKr6HlA.png)
+
+And Voila! A “web-server” written in OCaml, compiled down to JavaScript. It’s
+not much, but it’s a straight spike through a variety of technologies. Hopefully
+you find one or two of ’em interesting, and I encourage you to continue playing
+and exploring.
+
+### Going Forward
+
+Here are some more questions to ponder on.
+
+- Using micro, the first argument represents an instance of
+ `http.IncomingMessage`. This instance has a `url` property — **how would we go
+ about extracting the URL and displaying a different message?**
+- If we surround `Hello, world!` with `<strong></strong>`, we see that our
+ browser renders an HTML document. **Experiment with creating various
+ “template” functions** to build a Real Website™. (i.e. `fun req -> fun res -> layout req`)
+- Instead of returning a string, **use various methods on the **`res`**
+ parameter**, which is an instance of `http.ServerResponse`.
+
+You may also be interested in [Reason](http://facebook.github.io/reason/): a new
+syntax for OCaml developed at Facebook. It’s gaining a lot of traction in the
+JavaScript community, and even has
+[React bindings](https://reasonml.github.io/reason-react/)! I’m personally a
+huge fan of my friend Jared’s recent (excellent)
+[blog post about ReasonReact](https://jaredforsyth.com/2017/07/05/a-reason-react-tutorial/).
+
+In part 2, We’ll explore `@@bs.send.pipe` and how to better interface with
+chainable JavaScript APIs:
+
+[Typesafe JavaScript Chaining with OCaml and BuckleScript](/js-ocaml-chaining)
+
+I hope this serves as a gentle introduction to one of my favorite things
+happening in JavaScript right now. Go forth and explore, and be sure to share
+what you create.
diff --git a/hidden/2017-08-19-typesafe-javascript-chaining-with-ocaml-and-bucklescript.md b/hidden/2017-08-19-typesafe-javascript-chaining-with-ocaml-and-bucklescript.md
new file mode 100644
index 0000000..9d9ab22
--- /dev/null
+++ b/hidden/2017-08-19-typesafe-javascript-chaining-with-ocaml-and-bucklescript.md
@@ -0,0 +1,318 @@
+---
+title: Typesafe JavaScript Chaining with OCaml and BuckleScript
+route: /js-ocaml-chaining
+date: 2017-08-19
+description: Let's write some concise OCaml code to interface with JavaScript libraries that have a chainable API.
+---
+
+![OCaml code: express () |> get "/" index |> get "/about" about |> listen 1337](https://cdn-images-1.medium.com/max/1600/1*JE53vATHfCSAXLXrXSvgKA.png)
+
+In my [previous article](/js-ocaml-microservices), we explored how BuckleScript
+allows you to turn OCaml code into readable JavaScript, and how to interface
+with other modules in the JavaScript ecosystem.
+
+Today I’d like to continue on this path and show you the awesome
+`@@bs.send.pipe` binding attribute, which enables us to write concise OCaml code
+to interface with JavaScript libraries that have a chainable API.
+
+---
+
+### Exhibit A: Express
+
+To interface with the [express](https://expressjs.com/) Node.js web framework,
+we may write the following bindings in `src/FFI/Express.ml`. _(NOTE: Remember to
+include _`src/FFI`_ in the _`sources`_ field of _`bsconfig.json`_!)_
+
+ type app
+ external express : unit -> app = "" [@@bs.module]
+ external listen : app -> int -> unit = "" [@@bs.send]
+
+ type req
+ type res
+ external get : app -> string -> (req -> res -> res) -> unit = "" [@@bs.send]
+ external send : res -> string -> res = "" [@@bs.send]
+
+Then, in `src/index.ml` we could use this code as follows:
+
+ open Express
+
+ let app = express ();;
+
+ get app "/" (fun _ -> fun res ->
+ send res "Hello, world! <a href='/page'>Page 2</a>");;
+
+ get app "/page" (fun _ -> fun res ->
+ send res "Hey <a href='/'>Go back</a>");;
+
+ listen app 1337;;
+
+Running `bsb` results in the following `lib/js/src/index.js`:
+
+ // Generated by BUCKLESCRIPT VERSION 1.8.2, PLEASE EDIT WITH CARE
+ 'use strict';
+
+ var Express = require("express");
+
+ var app = Express();
+
+ app.get("/", (function (_, res) {
+ return res.send("Hello, world! <a href='/page'>Page 2</a>");
+ }));
+
+ app.get("/page", (function (_, res) {
+ return res.send("Hey <a href='/'>Go back</a>");
+ }));
+
+ app.listen(1337);
+
+ exports.app = app;
+ /* app Not a pure module */
+
+Nice! We can run `node lib/js/src/index.js` and get ourselves a running express
+server.
+
+### The Chaining Express API
+
+Consider the type we wrote for the `Express.get` function:
+
+ external get : app -> string -> (req -> res -> res) -> unit = "" [@@bs.send]
+
+`get` takes an `app` representing our express instance, a `string` for the path,
+a function (which takes a request and response), and returns a no-op (type
+`unit`).
+
+However — did you know we can _chain_ this API like so? In JavaScript:
+
+ app
+ .get("/", (req, res) => res.send("Hello, world!"))
+ .get("/about", (req, res) => res.send("About ..."))
+ .listen(1337)
+
+This pattern is very common in JS, and works in the following way: instead of
+`get` accepting an `app` and returning a `unit` (or no-op), we return another
+`app` which we can then use on a subsequent `get`!
+
+That’s a lot to unpack, so let’s demonstrate how to get from A to B in code.
+
+#### Step 1: Take an app, return an app
+
+ external get : app -> string -> (req -> res -> res) -> app = "" [@@bs.send]
+
+ let f: app = get (express ()) "/" index;;
+ let g: app = get f "/about" about;;
+ listen g 1337;;
+
+So what’s different here? First, we changed the return type of `get` from a
+`unit` to an `app`. Next we remove the definition for `app` and inline `express ()` in `f` directly.
+
+Then, instead of using `app` as the first argument for our second call to `get`,
+we pass in `f`. This is type-safe (remember: `f`, `g`, and `express ()` all have
+the same type) and sure enough if we compile this script and run it — we get a
+working Express app!
+
+In fact, if we wanted to, we could start combining some of these lines by
+inlining the definition for `f` entirely like so:
+
+ let g: app = get (get (express ()) "/" index) "/about" about;;
+ listen g 1337;;
+
+Or a step further, inlining `g` as well:
+
+ listen
+ (get
+ (get (express ()) "/" index)
+ "/about"
+ about)
+ 1337
+
+These two examples are _identical_ to the first, but notice that `app` is only
+referenced once in our code. Let’s peek at BuckleScript’s output
+`lib/js/src/index.js`:
+
+ Express().get("/", index).get("/about", about).listen(1337);
+
+🔗🔗🔗🔗🔗🔗🔗🔗!!!
+
+See, once we smush together our `get` and `listen` calls, there’s no need for
+temporary variables like `f` and `g`. BuckleScript knows this, and merely puts
+everything inline for us — in a “chained” manner.
+
+This may start to look a little LISP-y to you, and that’s fair — this syntax is
+not easier to read than our original example which specifies `app` multiple
+times. Let’s move on and see how we can clean up this code a little.
+
+#### Step 2: Some light plumbing, and a leak
+
+As we start composing functions (like we did by inlining `f` and `g` in the
+previous section), we’ll start to see quite a bit of parentheses. Consider the
+following bit of code:
+
+ apply_discount(
+ (get_age_group(get_age(user_from_id(id))))
+ price)
+
+Sure we can dress this up with further indentation, but developers reading this
+code will still construct a sort of “stack” in their head as they read the
+subsequent functions from left to right (_“Okay apply discount of the age group
+of the age of the…”_)
+
+To remedy this, OCaml provides the infix `|>` (or “pipe”) operator. We can
+inspect its type via `utop` :
+
+ utop # (|>);;
+ - : 'a -> ('a -> 'b) -> 'b = <fun>
+
+We see that we take an item of type `a`, a function from `a` to `b` and return
+an item of type `b`. \*_Exhale_ \*In code:
+
+ f(x) === x |> f
+
+And if we were to use this pipe multiple times:
+
+ f(g(x)) === x |> g |> f
+
+We can see here how the pipe operator (`|>`) allows us to unfold various layers
+of function composition. It’s quite neat, and leads to some very readable code.
+Let’s use it with our example above:
+
+ apply_discount(
+ (get_age_group(get_age(user_from_id(id))))
+ price)
+
+ (* turns into... *)
+
+ apply_discount(
+ (id |> user_from_id |> get_age |> get_age_group)
+ price)
+
+How about that last layer? What if we wanted to unfold `apply_discount` as well?
+
+ let f = id |> user_from_id |> get_age |> get_age_group |> apply_discount;;
+
+ f price;;
+
+Decent! However we hit a snag. `apply_discount` takes _two_ arguments, the
+user’s age group, and a price (`group -> price -> total`). If we were to write
+our code like so:
+
+ ... |> get_age_group |> apply_discount price
+
+We would receive a type error because `price` would be used as the _first_
+argument to `apply_discount`. This means we need some parentheses (technically
+you could use OCaml’s `@@`, but hold your horses), which we are trying to avoid!
+
+ (... |> get_age_group |> apply_discount) price
+
+One way to fix this? **Just make **`price`** the first argument!**
+
+#### Step 3: Save the app for last
+
+If we were to redefine `apply_discount` from `group -> price -> total` to `price -> group -> total`, we could then remove our parentheses entirely:
+
+ ... |> get_age_group |> apply_discount price
+
+Now price is used as the first argument, and second argument (the age group)
+makes its way to `apply_discount` from the pipeline.
+
+“Jordan this is great but I don’t really care about discounts and age groups,
+I’m trying to write a web server before my startup goes under.”
+
+Well fear no more, let’s return to our express example from earlier.
+
+ listen
+ (get
+ (get (express ()) "/" index)
+ "/about"
+ about)
+ 1337
+
+If we were to swap in some `|>` operators, we’ll quickly run into the same exact
+problem we had with `apply_discount`:
+
+ (((express () |> get) "/" index |> get) "/about" about |> listen) 1337
+
+Notice how `|>` doesn’t really buy us much. Since an `app` type must be the
+first argument to `get` and `listen`, we’re left with a confusing mix of
+parentheses and `|>` operators.
+
+As we learned in the previous section, our solution is to **move this argument
+to the end**. Let’s try it with some helper functions:
+
+ let get_ route handler app = get app route handler
+ let listen_ port app = listen app port
+
+And use ’em like so:
+
+ express () |>
+
+ get_ "/" index |>
+ get_ "/about" about |>
+
+ listen_ 1337
+
+And voila! An `app` type makes it way from `express ()`, through the pipe and
+onto the end of `get_ “/" index`. That method also returns an `app` type, which
+finds its way at the end of `get_ “/about" about`, and so on and so forth. We
+now have ourselves a beautiful, type-safe chain of functions that map to the
+chainable express API.
+
+ Express().get("/", index).get("/about", about).listen(1337);
+
+#### Step 4: BuckleScript can do this for us
+
+Defining a `function_` for every `function` you bind to JavaScript-land doesn’t
+sound all that exciting, though. Wouldn’t it be great if `get` and `listen`
+could work like that for us? Well they can!
+
+The current bindings for `get` and `listen` are defined using the `@@bs.send`
+attribute as follows:
+
+ external listen : app -> int -> unit = "" [@@bs.send]
+ external get : app -> string -> (req -> res -> res) -> app = "" [@@bs.send]
+
+However, BuckleScript also provides us with a `@@bs.send.pipe` which, you
+guessed it, allows us to define functions that work well with the `|>` operator.
+[From the docs](https://bucklescript.github.io/):
+
+> `bs.send.pipe` is similar to `bs.send` except that the first argument, i.e,
+> the object, is put in the position of last argument to help user write in a
+> _chaining style_:
+
+Here’s a modified binding for `get`:
+
+ external get : string -> (req -> res -> res) -> app = "" [@@bs.send.pipe: app]
+
+The difference here is that the first `app` in the type definition has been
+moved into the attribute, right after `@@bs.send.pipe:` . Here’s our new
+definition for `listen`:
+
+ external listen : int -> unit = "" [@@bs.send.pipe: app]
+
+Now, we can swap out `get_` and `listen_` in favor of their original
+counterparts.
+
+ express () |>
+
+ get "/" index |>
+ get "/about" about |>
+
+ listen 1337
+
+🎉🎉🎉🎉🎉🎉
+
+---
+
+### Closing Thoughts
+
+Okay so that was a lot of words to tell you how `@@bs.send.pipe` works, but I
+hope this post gave you a bit of intuition for why it exists and why you may
+want to use it. With that, here a few more questions to ponder on:
+
+- You may have noticed that the type of the callback for `get` is `req -> res -> res`. Why the second `res`? Well, express has
+ [operations](https://expressjs.com/en/4x/api.html#res.append) on `res` like
+ `send`, `status`, and `cookie` which are also chainable (they return a `res`
+ type). **Write chainable bindings for these methods.**
+- Imagine `@@bs.send.pipe` did not exist and we were stuck with our old
+ definitions of `get` and `listen`: could we create a function called
+ `make_chainable` where `make_chainable get === get_` and `make_chainable listen === listen_`? **Why or why not?** _(As a hint: what if _`get`_ and
+ _`listen`_ both had three arguments, could we do it then?)_
diff --git a/hidden/2018-01-07-j-fibonacci.md b/hidden/2018-01-07-j-fibonacci.md
new file mode 100644
index 0000000..07523dd
--- /dev/null
+++ b/hidden/2018-01-07-j-fibonacci.md
@@ -0,0 +1,258 @@
+---
+title: Hello, J! The Fibonacci Numbers
+route: /j-fibonacci
+date: 2018-01-07
+description: Today we're going to compute the Fibonacci numbers using a fun language called J.
+---
+
+Today we're going to compute the [Fibonacci numbers](https://en.wikipedia.org/wiki/Fibonacci_number) using a [fun language called J](https://en.wikipedia.org/wiki/J_(programming_language).
+
+You are welcome to [skip right to the action](#all-together-now-fibonacci) to see what this post is all about, but the following few paragraphs will introduce some J basics.
+
+## Hello, REPL!
+
+First thing's first, [go ahead and install J](http://code.jsoftware.com/wiki/System/Installation/All-in-One). I tend to run my code in `jconsole`, but `jqt` gives you a nice GUI with a package manager.
+
+In this post you'll find a number of code blocks. These represent lines from the J REPL. The top line, indented by two spaces, is our input. The line beneath is our output.
+
+```
+ 1 + 1
+2
+ 2 * 3 * 4 NB. this is a comment
+24
+ 'hello, world!'
+hello, world!
+```
+
+Easy enough, right?
+
+## Lists lists and more lists
+
+J really shines when it comes to lists - and we'll use 'em to build our sequence. You can build lists just by writing the elements separated with whitespace. Seriously.
+
+```
+ 1 2 3
+1 2 3
+```
+
+The `{.` verb (or function) is used to get the first element of a list. We can use it by just plopping it in the front.
+
+```
+ {. 1 2 3
+1
+ {. 10 17 100
+10
+```
+
+The `{:` verb is used to get the last element of the list.
+
+```
+ {: 1 2 3
+3
+ {: 10 17 100
+100
+ {: 1
+1
+```
+
+The `,` verb is used to join lists. You'll notice that this one is different in that it has stuff on both sides! Kinda like `+`.
+
+```
+ 1 , 2
+1 2
+ 1 2 3 , 4 5 6
+1 2 3 4 5 6
+```
+
+We can use the verb `+/` to sum the elements of a list.
+
+```
+ +/ 1 2 3
+6
+ +/ _1 5 NB. _ is for negative!
+4
+```
+
+## Stacking verbs
+
+We can use multiple verbs in an expression. Parentheses `()` help us control the order of things.
+
+```
+ {. 1 2 3
+1
+ {: 1 2 3
+3
+ ({. 1 2 3) , ({: 1 2 3)
+1 3
+ +/ ({. 1 2 3) , ({: 1 2 3)
+4
+```
+
+Let's kick things up by introducing "forks." J will automagically form a fork when we combine certain functions.
+
+```
+ ({. 1 2 3) , ({: 1 2 3)
+1 3
+ ({. , {:) 1 2 3
+1 3
+
+```
+
+See that? We were able to extract the `1 2 3` out as a single argument! J interprets the series of verbs `{.`, `,`, and `{:` as a "fork" - it sends our argument to the left and right sides, then takes the results and joins them with `,`. We can do this with all kinds of verbs.
+
+```
+ ({: , {.) 1 2 3
+3 1
+ ({: , {:) 1 2 3
+3 3
+ (+/ , {:) 1 2 3
+6 3
+ (+/ * {:) 1 2 3
+18
+```
+
+Cool, right? This helps us read out our expressions like sentences. For instance, "the sum times the last" can be written as `+/ * {:`, and our argument is automatically passed to both sides as it needs to be.
+
+## Defining verbs
+
+We can use the "is" verb `=:` to define our own verbs.
+
+```
+ (+/ , {:) 1 2 3
+6 3
+ sumlast =: +/ , {:
+ sumlast 1 2 3
+6 3
+ staticthing =: 55 NB. "nouns" too!
+ staticthing + 3
+58
+```
+
+We can also get a little more explicit with our verb definitions. The syntax of which is a little funky, but bear with me.
+
+```
+ sumlast =: +/ , {:
+ sumlast 1 2 3
+6 3
+ sumlast =: verb : '(+/ , {:) y'
+ NB. 'y' is our argument!
+ sumlast 1 2 3
+6 3
+```
+
+## All together now, Fibonacci
+
+Let's take what we've learned so far and start exploring.
+
+We can make a list.
+
+```
+ 1 2
+1 2
+```
+
+We can pick items from our list.
+
+```
+ {. 1 2
+1
+ {: 1 2
+2
+```
+
+The next [Fibonacci number](https://en.wikipedia.org/wiki/Fibonacci_number) is the sum of the previous two.
+
+```
+ +/ 1 2
+3
+ +/ 2 3
+5
+```
+
+We need the sum, but we also need to keep the second number around for next time.
+
+```
+ ({: 1 2) , (+/ 1 2)
+2 3
+ ({: 2 3) , (+/ 2 3)
+3 5
+```
+
+We can extract our argument using a fork to simplify our expression.
+
+```
+ ({: , +/) 1 2
+2 3
+ ({: , +/) 2 3
+3 5
+```
+
+We can stack our verbs multiple times.
+
+```
+ ({: , +/) 1 2
+2 3
+ ({: , +/) ({: , +/) 1 2
+3 5
+ ({: , +/) ({: , +/) ({: , +/) 1 2
+5 8
+```
+
+We can use the power verb `^:` to do this for us.
+
+```
+ (({: , +/)^:0) 1 2
+1 2
+ (({: , +/)^:1) 1 2
+2 3
+ (({: , +/)^:2) 1 2
+3 5
+ (({: , +/)^:5) 1 2
+13 21
+```
+
+We can start at `1 1`.
+
+```
+ (({: , +/)^:0) 1 1
+1 1
+ (({: , +/)^:1) 1 1
+1 2
+ (({: , +/)^:2) 1 1
+3 5
+ (({: , +/)^:5) 1 1
+8 13
+```
+
+We can take just the last number.
+
+```
+ {: (({: , +/)^:6) 1 1
+21
+ {: (({: , +/)^:20) 1 1
+17711
+```
+
+We can define a Fibonacci function.
+
+```
+ fib =: verb : '{: (({: , +/)^:y) 1 1'
+ fib 6
+21
+ fib 20
+17711
+ fib 50
+32951280099
+```
+
+## Closing notes
+
+I hope this gentle introduction alleviates some of the pain that comes with looking at an expression like `{: (({: , +/)^:20) 1 1` for the first time. Once you break it down, it's really not so special.
+
+J challenges what it means for code to be "readable." Terse by nature, it uses compact symbols and clever composition tools to let us write code that flows like a conversation.
+
+There's still so much to learn. J has a ton of great tools for representing matrices, polynomials, tables, and much more. If you find posts like these interesting, be sure to [let me know on twitter](https://twitter.com/jdan) so we can learn more together.
+
+Feel free to poke around the [complete J vocabulary](http://www.jsoftware.com/help/dictionary/vocabul.htm) to see what else this great language has to offer. The documentation is just as condensed as the language itself, so proceed with caution (and lots of patience).
+
+Thanks so much for reading!
diff --git a/hidden/2018-02-10-j-pascal.md b/hidden/2018-02-10-j-pascal.md
new file mode 100644
index 0000000..551a7b0
--- /dev/null
+++ b/hidden/2018-02-10-j-pascal.md
@@ -0,0 +1,279 @@
+---
+title: Hello, J! Pascal's Triangle
+route: /j-pascal
+date: 2018-02-10
+description:
+---
+
+Following [my previous post on the Fibonacci numbers](/j-fibonacci), here's a quick post on generating another famous mathematical sequence - [Pascal's Triangle](https://en.wikipedia.org/wiki/Pascal%27s_triangle).
+
+```
+ 1
+ 1 1
+ 1 2 1
+ 1 3 3 1
+1 4 6 4 1
+```
+
+## Quick Background
+
+Pascal's triangle contains numbers formed by binomial coefficients (often referred to as "N choose K"), but there's a simple and elegant way to generate it: **each item is the sum of the two numbers above it**.
+
+<pre>
+ 1 <span style="border:1px solid black">3 3</span> 1
+1 4 <u>6</u> 4 1
+</pre>
+
+We see here that the underlined 6 is formed by adding the two threes above it.
+
+<pre>
+ <span style="border:1px solid black">1 3</span> 3 <span style="border:1px solid black">1 </span>
+1 <u>4</u> 6 4 <u>1</u>
+</pre>
+
+Similarly, we see the 4 is formed by adding the 1 and 3, and the 1 is formed by adding 1 to nothing (0).
+
+## Summing pairs
+
+Before we get started go ahead and [grab yourself a copy of the J runtime](http://code.jsoftware.com/wiki/System/Installation/All-in-One) if you haven't already.
+
+Let's start by grabbing the first 5 integers using the ["integers" verb `i.`](http://www.jsoftware.com/help/dictionary/didot.htm)
+
+```
+ i. 5
+0 1 2 3 4
+```
+
+We can use the ["same" verb `]`](http://www.jsoftware.com/help/dictionary/d500.htm) to echo whatever we pass into it.
+
+```
+ ] 10
+10
+ ] i.5
+0 1 2 3 4
+```
+
+Our primary tool will be J's [infix adverb `\`](http://www.jsoftware.com/help/dictionary/d430.htm). This single backslash allows us to scan through a list of numbers in groups and apply a function to them.
+
+Let's scan every 3 items and pass them to the "same" verb `]`.
+
+```
+ 3 ]\ i.5
+0 1 2
+1 2 3
+2 3 4
+```
+
+We can see here that J looks through `0 1 2 3 4` in overlapping groups of 3. So we'll have `0 1 2`, followed by `1 2 3`, etc.
+
+By changing the number in front, we can scan in groups of 2.
+
+```
+ 2 ]\ i.5
+0 1
+1 2
+2 3
+3 4
+```
+
+Let's swap out our boring `]` for something more interesting: `+/`, which sums a list of numbers.
+
+```
+ 3 +/\ i.5
+3 6 9
+ +/ 0 1 2
+3
+ +/ 1 2 3
+6
+ +/ 2 3 4
+9
+```
+
+Similarly, we can do this pairwise.
+
+```
+ 2 +/\ i.5
+1 3 5 7
+```
+
+Pretty nifty right?
+
+## Row after row
+
+So why is this useful? Well, if we take another look at our triangle:
+
+```
+ 1
+ 1 1
+ 1 2 1
+ 1 3 3 1
+1 4 6 4 1
+```
+
+We can see that the next row, `1 5 10 10 5 1` can be formed by this exact strategy: summing each pair of numbers to generate the number beneath it.
+
+```
+ 2 +/\ 1 3 3 1
+4 6 4
+ 2 +/\ 1 4 6 4 1
+5 10 10 5
+```
+
+Well, almost. Our issue is that we're missing out on the 1s on either side! This is because our infix operator `\` starts with `+/ 1 4` to get 5 - skipping over the 1.
+
+To fix this, let's surround our row with 0s.
+
+```
+ 2 ]\ 0 1 3 3 1 0
+0 1
+1 3
+3 3
+3 1
+1 0
+ 2 +/\ 0 1 3 3 1 0
+1 4 6 4 1
+ 2 +/\ 0 1 4 6 4 1 0
+1 5 10 10 5 1
+```
+
+Much better. So how do we surround with 0s automatically?
+
+The [append verb `,`](http://www.jsoftware.com/help/dictionary/d320.htm) is used to join items and lists.
+
+```
+ 0 , 1
+0 1
+ 1 2 3 , 7 8 9
+1 2 3 7 8 9
+```
+
+The ["bond" verb `&`](http://www.jsoftware.com/help/dictionary/d630n.htm) allows us to partially evaluate a verb that normally expects items on both sides.
+
+```
+ 1 + 2
+3
+ (1&+) 2
+3
+ 4 % 2 NB. "%" is division!
+2
+ (%&2) 4
+2
+```
+
+We can bind our append verb to add a 0 to either side.
+
+```
+ (0&,) 5 6 7
+0 5 6 7
+ (,&0) 5 6 7
+5 6 7 0
+```
+
+We can also do both.
+
+```
+ (0&,) (,&0) 5 6 7
+0 5 6 7 0
+ (,&0) (0&,) 5 6 7
+0 5 6 7 0
+```
+
+Putting it all together: we can append 0 on either side of our row:
+
+```
+ (0&,) (,&0) 1 4 6 4 1
+0 1 4 6 4 1 0
+```
+
+And sum each pair:
+
+```
+ 2 +/\ (0&,) (,&0) 1 4 6 4 1
+1 5 10 10 5 1
+```
+
+## All together now
+
+Let's wrap this in our own verb called `next`. "`y`" gives us access to the argument(s) passed in.
+
+```
+ next =: verb : '2 +/\ (0&,) (,&0) y'
+ next 1 3 3 1
+1 4 6 4 1
+ next 1 4 6 4 1
+1 5 10 10 5 1
+```
+
+Fortunately, `next` works just fine on the first row of our triangle: a single `1`.
+
+```
+ next 1
+1 1
+```
+
+We can apply `next` multiple times.
+
+```
+ next 1
+1 1
+ next 1 1
+1 2 1
+ next next 1
+1 2 1
+ next next next next 1
+1 4 6 4 1
+```
+
+We can use the ["power" verb `^:`](http://www.jsoftware.com/help/dictionary/d202n.htm) to do this for us.
+
+```
+ (next^:0) 1
+1
+ (next^:1) 1
+1 1
+ (next^:5) 1
+1 5 10 10 5 1
+```
+
+We can also pass a list to `^:` to generate multiple powers at the same time.
+
+```
+ (next^:(i.7)) 1
+1 0 0 0 0 0 0
+1 1 0 0 0 0 0
+1 2 1 0 0 0 0
+1 3 3 1 0 0 0
+1 4 6 4 1 0 0
+1 5 10 10 5 1 0
+1 6 15 20 15 6 1
+```
+
+We can store this as another verb to generate Pascal's Triangle. We’ll also simplify our definition of next using the ["atop" verb `@`](http://www.jsoftware.com/help/dictionary/d620.htm).
+
+```
+ next =: 2 +/\ (0&,) @ (,&0)
+ pascal =: verb : '(next^:(i.y)) 1'
+ NB. We can also inline `next` entirely
+ pascal =: verb : '((2 +/\ (0&,) @ (,&0))^:(i.y)) 1'
+ pascal 10
+1 0 0 0 0 0 0 0 0 0
+1 1 0 0 0 0 0 0 0 0
+1 2 1 0 0 0 0 0 0 0
+1 3 3 1 0 0 0 0 0 0
+1 4 6 4 1 0 0 0 0 0
+1 5 10 10 5 1 0 0 0 0
+1 6 15 20 15 6 1 0 0 0
+1 7 21 35 35 21 7 1 0 0
+1 8 28 56 70 56 28 8 1 0
+1 9 36 84 126 126 84 36 9 1
+```
+
+🎉 🎉
+
+Thanks to the powers of `\` and `^:`, we can intuitively generate Pascal's Triangle in a few dozen characters.
+
+J makes operations on lists and matrices a breeze, and contains a number of interesting ideas for function composition and application. I hope you found this post interesting and give a look at what J has to offer.
+
+I think you'll be pleasantly surprised at how fun it is.
+
+Thanks for reading!
diff --git a/hidden/2019-04-13-programming-languages.md b/hidden/2019-04-13-programming-languages.md
new file mode 100644
index 0000000..9a84ede
--- /dev/null
+++ b/hidden/2019-04-13-programming-languages.md
@@ -0,0 +1,511 @@
+---
+title: A Frontend Programmer's Guide to Languages
+route: /programming-languages
+date: 2019-04-13
+description: Today, we're going to make a programming language. No, it won't take a PhD, or years of your time (or even days for that matter). You and I, together, are going to learn what a programming language is and actually build one that can evaluate programs.
+---
+
+Today, we're going to make a programming language.
+
+No, it won't take a PhD, or years of your time (or even days for that matter). You and I, together, are going to learn what a programming language _is_ and actually build one that can evaluate programs.
+
+By the end of this post we'll have written a JavaScript function, `evaluate`, which interprets a small programming language with strings and variables. You won't be going off and rewriting your stack in it (though you're certainly welcome to try!), but I hope it's a fun exercise.
+
+I encourage you to copy the code examples in your editor of choice and actually run them. You should be able to make it through this post in a single listen of [Lights - Little Machines](https://open.spotify.com/album/1u8OmwItT46Y1gD2xKAK9D?si=CcY4GgT4TGyOa7eNcnLqTA).
+
+## Hello, world!
+
+To kick things off, we'll write an interpreter for a new HelloWorld language, and use it write a [Hello, world! program](https://en.wikipedia.org/wiki/%22Hello,_World!%22_program).
+
+Here's our goal:
+
+```js
+const program = { type: "HelloWorld" }
+console.log(evaluate(program))
+// => Hello, world!
+```
+
+I'm sure you have questions. _Real quick_, let's introduce some terms that we'll more clearly define as we go.
+
+- Our "**program**" is represented by the JavaScript object `{ type: "HelloWorld" }`. Throughout this post, we'll be adding more and more things to this object. For now, there's not much to it.
+- Our "**evaluator**" (or "interpreter" - it's your call) is a single JavaScript function that accepts a "**program**."
+- We receive a "**value**" from our "**evaluator**," which we send to the wonderful `console.log` function you're all familiar with.
+
+Now let's build our **evaluator**.
+
+```js
+function evaluate(node) {
+ switch (node.type) {
+ case "HelloWorld":
+ return "Hello, world!"
+ default:
+ throw `evaluate -- unknown node type ${node.type}`
+ }
+}
+```
+
+This function is not terribly interesting, but I'd like to call out a few details.
+
+- Our function accepts a parameter that we call `node` as in, a **node** of a tree. (_dramatic foreshadowing_)
+- The crux of this function is a single `switch` statement that operates on the `type` field of our node. Our language is very simple, so we only have a single "node type" (namely, "HelloWorld").
+- For the "HelloWorld" **expression** - we return the string "Hello, world!"
+- If we see something we don't recognize - we throw an error. The programmer messed up!
+
+At this point we have 8 lines of code that evaluate a simple HelloWorld language. I'll emphasize the _simple_ here, there are no variables, no loops, no modules, not even numbers - but it's a language. Our language has a very small **grammar**, but it's a language.
+
+Let's make things more interesting.
+
+## Strings
+
+In this section we'd like to make a new `HelloStrings` language (marketing wants us to be able to print more than just "Hello, world!") that can produce strings and run two operations on them.
+
+Here's our goal for this section:
+
+```js
+console.log(
+ evaluate({
+ type: "String",
+ content: "Apple",
+ })
+)
+// => Apple
+
+console.log(
+ evaluate({
+ type: "Excite",
+ expression: {
+ type: "String",
+ content: "Banana",
+ },
+ })
+)
+// => Banana!
+
+console.log(
+ evaluate({
+ type: "Append",
+ first: {
+ type: "String",
+ content: "Apple",
+ },
+ second: {
+ type: "Excite",
+ expression: {
+ type: "String",
+ content: "Banana",
+ },
+ },
+ })
+)
+// => AppleBanana!
+```
+
+Some initial observations:
+
+- We ditch our "HelloWorld" **expression** in favor of "String," which has a "content" field in addition to the "type" field we used in the previous section.
+- We introduce "Excite" which adds exclamation points to expressions.
+- We introduce "Append," which also contains expressions in its definition - two of 'em in fact!
+
+Let's start off by creating an **evaluator** to operate on "String" expressions.
+
+```js
+function evaluate(node) {
+ switch (node.type) {
+ case "String":
+ return node.content
+ default:
+ throw `evaluate -- unknown node type ${node.type}`
+ }
+}
+```
+
+Great, so we replace "HelloWorld" with "String" as the node's type - and return its "content" value instead of the string "Hello, world!"
+
+**_You just wrote a programming language with strings, let's celebrate!_**
+
+But if we continue with our desired goal above, we see the following:
+
+```
+Apple
+evaluate -- unknown node type Excite
+```
+
+We can evaluate `{ type: "String", content: "Apple" }` no problem, but we don't know what to do with this "Excite" thing just yet.
+
+So how might we evaluate "Excite" expressions? Based on how it produces "Banana!" in our desired output, we may be inclined to say that "Excite" _takes a string and adds an exclamation point to it_. Simple enough, right? But let's take a closer look.
+
+```js
+{
+ type: "Excite",
+ expression: {
+ type: "String",
+ content: "Banana"
+ }
+}
+```
+
+The "expression" field of our Excite expression node isn't a string per se, but an expression in the HelloStrings language! **Our expression contains an expression inside of it!** (Are you beginning to see why our evaluator accepts a `node` as in "nodes of a tree"?)
+
+To illustrate this further, allow me to propose the following, _valid_ program for the HelloStrings language.
+
+```js
+console.log(
+ evaluate({
+ type: "Excite",
+ expression: {
+ type: "Excite",
+ expression: {
+ type: "String",
+ content: "Banana",
+ },
+ },
+ })
+)
+```
+
+In the above example, we'll evaluate the string "Banana" - excite it to get "Banana!" - than excite _that_ to get "Banana!!" (how very exciting!)
+
+Let's begin adding Excite expressions to our language.
+
+```js
+function evaluate(node) {
+ switch (node.type) {
+ case "String":
+ return node.content;
+ case "Excite":
+ // We have this node.expression thing, but what do
+ // we do with it?
+ ???
+ default:
+ throw `evaluate -- unknown node type ${node.type}`;
+ }
+}
+```
+
+Rather than just returning a "value" like we did for String expressions (`return node.content`), we'll need to use `node.expression` somehow. As the name suggests, `node.expression` is an expression, and what do we do with expressions?
+
+We evaluate them!
+
+```js
+case "Excite":
+ return evaluate(node.expression) + "!";
+```
+
+`evaluate` is now a recursive function which operates on String and Excite expressions. Here's the function in all its glory and an invitation to implement `Append` yourself.
+
+```js
+function evaluate(node) {
+ switch (node.type) {
+ case "String":
+ return node.content
+ case "Excite":
+ return evaluate(node.expression) + "!"
+ case "Append":
+ // Now it's your turn, how might we evaluate
+ // Append expressions?
+ default:
+ throw `evaluate -- unknown node type ${node.type}`
+ }
+}
+```
+
+## Variables
+
+At this point we have a small language which can produce strings with "Excite" and "Append" statements. Nice job if you've made it this far!
+
+Let's use this momentum and expand our language to support a very popular language construct - **variables**.
+
+By the end of this section, we'll have a language which can declare and retrieve the value of variables in HelloStrings expressions.
+
+```js
+console.log(
+ evaluate(
+ {
+ type: "Let",
+ name: "x",
+ value: {
+ type: "String",
+ content: "Hello, world",
+ },
+ expression: {
+ type: "Excite",
+ expression: {
+ type: "Variable",
+ name: "x",
+ },
+ },
+ },
+ {}
+ )
+)
+// => Hello, world!
+```
+
+There's a lot there, so let's break it down.
+
+- `evaluate` now takes a second argument (_dramatic foreshadowing_)
+- We introduce a new "Variable" expression, which will **lookup** a variable by its name.
+- We introduce a "Let" expression, which makes a new variable with a "name" and a "value" (the value being an expression in our language!), and uses that when evaluating an inner **expression**.
+
+In our case, we're setting "x" to the evaluation of a "Hello, world" String, then retrieving the value of "x" to Excite it.
+
+Let's start by adding "Variable" expressions to `evaluate`.
+
+```js
+function evaluate(node) {
+ switch (node.type) {
+ case "Variable":
+ // We have `node.name`, but what to do with it?
+ ...
+ }
+}
+```
+
+Consider the following code:
+
+```js
+evaluate({ type: "Variable", name: "x" })
+```
+
+Here we want to evaluate the variable `x`, but there doesn't seem to be much to work with. In a typical programming language, what we might we do with a variable?
+
+We look it up!
+
+```js
+function evaluate(node) {
+ switch (node.type) {
+ case "Variable":
+ return lookup(node.name);
+ ...
+ }
+}
+
+```
+
+In order for `lookup` to do anything meaningful, we need to provide it with some sort of **environment** - a collection of variable names and values. We'll call this `env`.
+
+```js
+function evaluate(node) {
+ switch (node.type) {
+ case "Variable":
+ return lookup(env, node.name);
+ ...
+ }
+}
+
+```
+
+And where does `env` come from? Rather unfortunately we can't make it out of thin air (believe me, I tried for many hours), but we _can_ **pass it in to our evaluator.**
+
+```js
+function evaluate(node, env) {
+ switch (node.type) {
+ case "Variable":
+ return lookup(env, node.name)
+ case "String":
+ return node.content
+ case "Excite":
+ return evaluate(node.expression, env) + "!"
+ case "Append":
+ // Left as an exercise to the reader
+ default:
+ throw `evaluate -- unknown node type ${node.type}`
+ }
+}
+```
+
+_Remember to add `env` to our recursive calls in Excite and Append (you did implement Append, right?)_
+
+Let's define `lookup`. For simplicity's sake, we'll say an environment is a JavaScript object with `name` keys and `value` values.
+
+```js
+function lookup(env, name) {
+ return env[name]
+}
+```
+
+Simple, right?
+
+```js
+console.log(evaluate({ type: "Variable", name: "x" }, { x: "Hello, world!" }))
+// => Hello, world!
+```
+
+**Congratulations you just added variables to your language!**
+
+Last but not least, we'll want an easy way to create new variables. Without this, we'll be evaluating Excite's and Append's with nothing more than a bunch of global variables - and the marketing department _definitely_ doesn't want that.
+
+Let's start by listing what a "Let" expression actually contains:
+
+```js
+{
+ type: "Let",
+ name: "x",
+ value: {
+ type: "String",
+ content: "Hello, world"
+ },
+ expression: {
+ type: "Excite",
+ expression: {
+ type: "Variable",
+ name: "x"
+ }
+ }
+}
+```
+
+- A "Let" type
+- A "name" field for naming our new variable
+- A "value" field for giving our new variable a value
+- An "expression" field to use our new variable in!
+
+Excellent, now let's get started on implementing the thing.
+
+```js
+function evaluate(node, env) {
+ switch (node.type) {
+ case "Variable":
+ return lookup(env, node.name);
+ case "Let":
+ let newEnv = ???
+ return evaluate(node.expression, newEnv);
+ ...
+ }
+}
+```
+
+We'll be **adding a new variable to our environment**, and using that to evaluate our expression. For simplicity's sake, let's give ourselves an `extendEnv` function to do this.
+
+```js
+function evaluate(node, env) {
+ switch (node.type) {
+ case "Variable":
+ return lookup(env, node.name);
+ case "Let":
+ let name = ???
+ let value = ???
+ let newEnv = extendEnv(env, name, value)
+ return evaluate(node.expression, newEnv);
+ ...
+ }
+}
+```
+
+`name` is simple, that's just `node.name`. For `value`, however, we'll be given a _HelloStrings expression_ to evaluate.
+
+```js
+{
+ type: "Let",
+ name: "x",
+ value: {
+ type: "String",
+ content: "Hello, world"
+ },
+ ...
+}
+```
+
+Still, it's not much code :)
+
+```js
+function evaluate(node, env) {
+ switch (node.type) {
+ case "Variable":
+ return lookup(env, node.name);
+ case "Let":
+ let name = node.name;
+ let value = evaluate(node.value, env);
+ let newEnv = extendEnv(env, name, value);
+ return evaluate(node.expression, newEnv);
+ ...
+ }
+}
+```
+
+Finally, `extendEnv`.
+
+Our `env` is a simple JavaScript object that maps `names` to `values`. We'll need to extend an `env` with a _new_ name and value pair. We can do so with `Object.assign`:
+
+```js
+function extendEnv(env, name, value) {
+ let envAddition = {}
+ envAddition[name] = value
+
+ return Object.assign({}, env, envAddition)
+}
+```
+
+Or, using some of the latest and greatest JavaScript features:
+
+```js
+function extendEnv(env, name, value) {
+ return {
+ ...env,
+ [name]: value,
+ }
+}
+```
+
+Putting it all together, the combination of `lookup`, `extendEnv`, and `evaluate` completes the language.
+
+```js
+function lookup(env, name) {
+ return env[name]
+}
+
+function extendEnv(env, name, value) {
+ return {
+ ...env,
+ [name]: value,
+ }
+}
+
+function evaluate(node, env) {
+ switch (node.type) {
+ case "String":
+ return node.content
+ case "Excite":
+ return evaluate(node.expression, env) + "!"
+ case "Append":
+ // Left as an exercise to the reader
+ case "Variable":
+ return lookup(env, node.name)
+ case "Let":
+ let inner = node.expression
+ let value = evaluate(node.value, env)
+ let newEnv = extendEnv(env, node.name, value)
+ return evaluate(node.expression, newEnv)
+ default:
+ throw `evaluate -- unknown node type ${node.type}`
+ }
+}
+```
+
+## Closing notes and things to ponder
+
+If you made it to the end of this post, congratulations and thank you :)
+
+We developed a language and an evaluator that processes [Abstract Syntax Trees](https://en.wikipedia.org/wiki/Abstract_syntax_tree) (or ASTs for short, but I just called 'em nodes for simplicity). Our language has [constants](https://en.wikipedia.org/wiki/Constant_(computer_programming), some small expressions to operate on strings, and [variables](https://en.wikipedia.org/wiki/Variable_(computer_science).
+
+But this is only the beginning! You are ending this article with an `evaluate` function that can grow to support all sorts of common language features (numbers, comments, functions), the only limit is your imagination.
+
+I'd like to leave you with a few concrete exercises, should you wish to revisit your new language on a rainy day:
+
+- How might we add Numbers to our language? How about operations to Add and Multiply them? What about _rational_ numbers (fractions with a numerator and a denominator)?
+- As we saw earlier, we can kickstart our `env` by populating it with global values. Could we add functions to this environment? How might we call them from our language?
+- Can we take our abstract syntax tree and use it to generate JavaScript code? How about Ruby code?
+- Writing our programs in as JavaScript objects doesn't seem to scale well. How might we go about creating a **syntax** for our language and converting that to an AST?
+
+I'll also go ahead and plug some of my favorite resources on Programming Languages.
+
+- Dan Grossman's [Programming Languages on Coursera](https://www.coursera.org/learn/programming-languages) is an entirely free, entirely amazing three-part course for learning the ins and outs of different types of programming languages. I can't recommend it enough.
+- This article is inspired in no short part by [@jamiebuilds's super tiny compiler](https://github.com/jamiebuilds/the-super-tiny-compiler). I encourage you to check out the code to see some of the concepts introduced in this article in more detail.
+- [Crafting Interpreters](https://craftinginterpreters.com/) is an effort to build "a handbook for making programming languages." An ambitious goal, but it's completely free and chock full of different interpreter concepts.
+
+That's all for now. If you liked this article feel free to share it and follow me on Twitter while you're at it: [@jdan](https://twitter.com/jdan).
+
+You should also come work with me at [Stripe](https://stripe.com/jobs). We're hiring all over the place.
+
+Thanks again for reading,<br />
+Jordan
diff --git a/hidden/2019-05-10-infinite-lists.md b/hidden/2019-05-10-infinite-lists.md
new file mode 100644
index 0000000..5eee5c5
--- /dev/null
+++ b/hidden/2019-05-10-infinite-lists.md
@@ -0,0 +1,682 @@
+---
+title: Operating on Infinite Lists
+route: /infinite-lists
+date: 2019-05-10
+---
+
+Today we're going to step through one of my favorite exercises in composing functions - building and operating on infinite streams of data.
+
+The question we'll be answering is: How might you represent the following operations?
+
+```js
+nums
+// => 1, 2, 3, 4, ...
+double(nums)
+// => 2, 4, 6, 8, ...
+fib
+// => 0, 1, 1, 2, 3, 5, 8, ...
+```
+
+This post contains code written in JavaScript, but the ideas here apply to any language with functions and lists.
+
+## The Finite
+
+We can build a finite list in JavaScript like so:
+
+```js
+const ls = [1, 2, 3, 4, 5]
+// => [1, 2, 3, 4, 5]
+```
+
+We can access any element inside of it as well as its length:
+
+```js
+ls[0]
+// => 1
+ls[10]
+// => undefined
+ls.length
+// => 5
+```
+
+Alternatively, we can self-referentially represent lists as **an element, followed by a list**. This is commonly referred to as a "linked list." (You're welcome to scroll to the next section if this is familiar to you. You're welcome either way I guess, I'm not a cop.)
+
+In this data structure we'll use an object with a `first` field for the element and a `rest` field for the rest of the list. We'll use `null` for the empty list - where our list ends.
+
+```js
+const ll = {
+ first: 0,
+ rest: {
+ first: 1,
+ rest: {
+ first: 2,
+ rest: null,
+ },
+ },
+}
+```
+
+This is conceptually simpler, but pretty awkward (imagine if you had to use this instead of `[...]`!). Still, we can access elements inside of it:
+
+```js
+ll.first
+// => 0
+ll.rest.first
+// => 1
+ll.rest.rest.first
+// => 2
+```
+
+And we can compute its length using recursion (where our function is defined in terms of itself):
+
+```js
+function length(ll) {
+ if (ll === null) {
+ // empty list has length 0
+ return 0
+ } else {
+ // its length is 1 (the item)
+ // plus the length of the rest
+ return 1 + length(ll.rest)
+ }
+}
+length(ll)
+// => 3
+```
+
+## The Infinite
+
+Conceptually, an infinite list is a lot like a finite list. We'll want a way to get an item, and a way to get the rest. Let's kick things off by building a stream of ones:
+
+```js
+1, 1, 1, 1, ...
+```
+
+Our `first` will be a single piece of data in our stream - in this case, the number 1.
+
+```js
+const ones = {
+ first: 1,
+ rest: ???,
+}
+```
+
+In our linked list above, our `rest` was also a linked list. Similarly, the `rest` of our stream will be... a stream!
+
+```js
+const ones = {
+ first: 1,
+ rest: ones,
+}
+```
+
+You'll quickly find however, that we can't just define a variable in terms of itself. In JavaScript, we'll receive the following from our console:
+
+```
+ReferenceError: ones is not defined
+```
+
+What _can_ we define in terms of itself? Functions! Remember `length` from above?
+
+```js
+function length(ll) {
+ if (ll === null) {
+ return 0
+ } else {
+ return 1 + length(ll.rest)
+ }
+}
+```
+
+So...**let's switch `ones` to be a function**:
+
+```js
+function ones() {
+ return {
+ first: 1,
+ rest: ones(),
+ }
+}
+```
+
+But, we're not in the clear just yet:
+
+```js
+> ones()
+RangeError: Maximum call stack size exceeded
+ at ones (repl:1:14)
+ at ones (repl:4:11)
+ at ones (repl:4:11)
+```
+
+Uh oh. When creating our stream, `ones` is **eagerly** making subsequent calls to itself over and over again - never coming up for air before our JavaScript console lets us know we probably messed up.
+
+Instead, let's make our implementation **lazier** by returning the `ones` function and calling it when we need it.
+
+```js
+function ones() {
+ return {
+ first: 1,
+ rest: ones,
+ }
+}
+
+ones()
+// => { first: 1, rest: [Function: ones] }
+```
+
+No infinite loops! We can create a ones stream, and gather elements from it like so:
+
+```js
+ones().first
+// => 1
+ones().rest().first
+// => 1
+ones()
+ .rest()
+ .rest().first
+// => 1
+```
+
+## Defining and Building Streams
+
+Taking a step back, we can define a **stream** as a function which returns two things:
+
+(1) an item<br>
+(2) a stream
+
+We can define a `twos` stream similarly to our `ones`:
+
+```js
+function twos() {
+ return {
+ first: 2,
+ rest: twos,
+ }
+}
+
+twos().first
+// => 2
+twos().rest().first
+// => 2
+twos()
+ .rest()
+ .rest().first
+// => 2
+```
+
+Still, these streams don't seem particularly _useful_. One potential smell is our streams are functions, but so far they haven't taken any arguments. What if they did?
+
+Let's kick things off with a stream of the natural numbers.
+
+```js
+function nums(n) {
+ return {
+ first: n,
+ // Remember, rest is a stream,
+ // and streams are functions.
+ rest: () => nums(n + 1),
+ }
+}
+
+nums(1).first
+// => 1
+nums(1).rest().first
+// => 2
+nums(1)
+ .rest()
+ .rest().first
+// => 3
+nums(999).first
+// => 999
+```
+
+An initial argument for `nums` doesn't seem particuarly useful, so let's use a default.
+
+```js
+function nums(n = 1) {
+ return {
+ first: n,
+ // Remember, rest is a stream,
+ // and streams are functions.
+ rest: () => nums(n + 1),
+ }
+}
+
+nums().first
+// => 1
+```
+
+All these `.rest()`/`.first` chains are becoming cumbersome, so let's take a second and define a function to grab the first `n` elements of a stream.
+
+```js
+function take(stream, n) {
+ if (n <= 0) {
+ return []
+ } else {
+ const { first, rest } = stream()
+ return [first].concat(take(rest, n - 1))
+ }
+}
+
+take(nums, 5)
+// => [1, 2, 3, 4, 5]
+```
+
+**Exercise: Write a function to grab the `nth` item of a stream**
+
+```js
+nth(nums, 100)
+// => 101
+```
+
+<details>
+<summary>Solution</summary>
+
+<pre><code>function nth(stream, n) {
+ if (n < 0) return null
+
+ const {first, rest} = stream()
+ if (n === 0) {
+ return first
+ } else {
+ return nth(rest, n - 1)
+ }
+}
+</code></pre>
+</details>
+
+At this point we have a stream with some interesting data in it.
+
+Now it's your turn. Try the following examples on your own machine. The solutions can be expanded if you get stuck (and it's okay if you do!)
+
+**Define a stream which returns the odd numbers.**
+
+```
+take(odds, 5)
+// => [1, 3, 5, 7, 9]
+```
+
+<details>
+<summary>Solution</summary>
+<pre><code>function odds(n = 0) {
+ return {
+ first: 2 * n + 1,
+ rest: () => odds(n + 1),
+ }
+}
+</code></pre>
+</details>
+
+**Define a stream which returns the square numbers.**
+
+```js
+take(squares, 5)
+// => [1, 4, 9, 16, 25]
+```
+
+<details>
+<summary>Solution</summary>
+
+<pre><code>function squares(n = 1) {
+ return {
+ first: n * n,
+ rest: () => squares(n + 1),
+ }
+}
+</code></pre>
+</details>
+
+**Define a stream which loops between 0 and 1.**
+
+```js
+take(flipFlop, 5)
+// => [0, 1, 0, 1, 0]
+```
+
+<details>
+<summary>Solution</summary>
+
+<pre><code>function flipFlop(n = 0) {
+ return {
+ first: n % 2,
+ rest: () => flipFlop(n + 1),
+ }
+}
+</code></pre>
+</details>
+
+Right on. Feel free to take a break, refill your water, and we'll continue onto the next section - writing functions to operate on our streams.
+
+## Streams Out / Streams In
+
+Now that we're comfortable creating streams, let's start writing functions that can create streams for us.
+
+To kick things off, let's write a function that takes in a number and **returns a stream** of that number.
+
+```js
+function fixed(n) {
+ // Streams are functions
+ return () => ({
+ // Which return an item...
+ first: n,
+
+ // ...and a stream
+ rest: fixed(n),
+ })
+}
+
+const sevens = fixed(7)
+take(sevens, 5)
+// => [7, 7, 7, 7, 7]
+```
+
+Our pattern here is a little different than the `ones` and `twos` from earlier - in particular we're **returning a function** instead of an object with `first` and `rest`.
+
+Additionally, our value for `rest` is a little simpler since `fixed(n)` returns a function (we don't need to make a new one inline like before).
+
+For our next example, let's recall our definitions for `odds`, `squares`, and `flipFlop`. Specifically, just the `first` and `rest` parts of each.
+
+```
+odds
+ first: 2 * n + 1,
+ rest: () => odds(n + 1),
+
+squares
+ first: n * n,
+ rest: () => squares(n + 1),
+
+flipFlop
+ first: n % 2,
+ rest: () => flipFlop(n + 1),
+```
+
+Interestingly, our three streams share the same `rest`! And the `first` is just a function of `n`.
+
+Let's materialize this some more, by building a function `funcStream` to generalize our three examples.
+
+```js
+function funcStream(f, n = 0) {
+ return () => ({
+ first: f(n),
+ rest: funcStream(f, n + 1),
+ })
+}
+
+take(funcStream(n => 2 * n + 1), 5)
+// => [1, 3, 5, 7, 9]
+take(funcStream(n => n * n), 5)
+// => [0, 1, 4, 9, 16]
+take(funcStream(n => n % 2), 5)
+// => [0, 1, 0, 1, 0]
+```
+
+Not bad - but we can do better. `funcStream` still needs to keep track of its own `n` and update it appropriately, but `nums` can do that for us. **What if our function operated on `nums`, or _any stream_ for that matter?**
+
+We can think of this as a "map" equivalent for streams. Here's how we might do it:
+
+```js
+function map(f, stream) {
+ return () => {
+ const { first, rest } = stream()
+
+ return {
+ first: f(first),
+ rest: map(f, rest),
+ }
+ }
+}
+
+take(map(n => 2 * n, nums), 5)
+// => [0, 2, 4, 6, 8]
+```
+
+Using this, we can define functions to operate on streams:
+
+```js
+const double = stream => map(n => 2 * n, stream)
+take(double(nums), 5)
+// => [0, 2, 4, 6, 8]
+```
+
+Better yet, these functions can **compose**.
+
+```js
+take(double(double(nums)), 5)
+// => [0, 4, 8, 12, 16]
+```
+
+Let's take stock. `nums` is an **infinite stream of numbers**, and we're able to apply a function to it. Under the hood, this function is **lazily** invoked when we need it - such as when we display the output with `take`.
+
+But mapping isn't the only thing we do with finite lists, so it shouldn't be the only thing we do with streams. **What if we wanted to filter a stream?**
+
+```js
+take(filter(n => n % 3 > 0, nums), 6)
+// => [1, 2, 4, 5, 7, 8]
+```
+
+When filtering a stream, we'll grab it's `first` and `rest`, then test `first`. If the test passes (and the function passed into `filter` returns true), we'll make sure to include that in the stream.
+
+If the test does not pass, we'll just filter the `tail` and ignore the `first`. Let's turn this into code:
+
+```js
+function filter(f, stream) {
+ const { first, rest } = stream()
+ if (f(first)) {
+ // Streams are functions
+ return () => ({
+ first: first,
+ rest: filter(f, rest),
+ })
+ } else {
+ // Ignore first, filter rest
+ return filter(f, rest)
+ }
+}
+```
+
+And just like that, we now have a `filter` that operates on an infinite stream of data.
+
+```js
+take(filter(n => n % 2 > 0, nums), 5)
+// => [1, 3, 5, 7, 9]
+take(filter(_ => true, nums), 5)
+// => [0, 1, 2, 3, 4]
+take(filter(_ => Math.random() < 0.05, nums), 5)
+// => [28, 31, 33, 37, 54]
+```
+
+## Closing Exercises
+
+Before you go, try your hand at the following exercises.
+
+**Using `map`, create a [FizzBuzz](https://en.wikipedia.org/wiki/Fizz_buzz) stream.**
+
+```js
+take(fizzbuzz, 5)
+// => [1, 2, 'Fizz', 4, 'Buzz']
+```
+
+<details>
+<summary>Solution</summary>
+
+<pre><code>const fizzbuzz = map(n => {
+ if (n % 15 === 0) {
+ return 'FizzBuzz'
+ } else if (n % 3 === 0) {
+ return 'Fizz'
+ } else if (n % 5 === 0) {
+ return 'Buzz'
+ } else {
+ return n
+ }
+}, nums)
+</code></pre>
+</details>
+
+**Using `map` and `nums`, write a function that takes two values and produces a stream that toggles between them.**
+
+```js
+take(toggle("hi", "ho"), 5)
+// => ['hi', 'ho', 'hi', 'ho', 'hi']
+```
+
+<details>
+<summary>Solution</summary>
+
+<pre><code>function toggle(a, b) {
+ return map(
+ // nums starts at 1
+ n => (n - 1) % 2 === 0 ? a : b,
+ nums
+ )
+}
+</code></pre>
+</details>
+
+**Using `map` and `nums`, write a function that takes an array of values and produces a stream that cycles between them.**
+
+```js
+take(cycle(["a", "b", "c"]), 5)
+// => ['a', 'b', 'c', 'a', 'b']
+```
+
+<details>
+<summary>Solution</summary>
+
+<pre><code>function cycle(items) {
+ return map(
+ // nums starts at 1
+ n => items[(n - 1) % items.length],
+ nums
+ )
+}
+</code></pre>
+</details>
+
+**Write a function that takes two streams and interleaves them.**
+
+```js
+take(interleave(fixed(0), fixed(9)), 5)
+// => [0, 9, 0, 9, 0]
+```
+
+<details>
+<summary>Solution</summary>
+
+<pre><code>function interleave(a, b) {
+ const aPair = a()
+ const bPair = b()
+
+ return () => ({
+ first: aPair.first,
+ rest: () => ({
+ first: bPair.first,
+ rest: interleave(aPair.rest, bPair.rest),
+ })
+ })
+}
+</code></pre>
+</details>
+
+**Write a function which accepts a stream and returns a new stream whose values are a running total of the original stream.**
+
+For `nums`: return 1, then 1 + 2, then 1 + 2 + 3, then 1 + 2 + 3 + 4, etc.
+
+```js
+take(total(nums), 6)
+// => [1, 3, 6, 10, 15, 21]
+```
+
+_Hint: give `total` a second argument that defaults to 0_
+
+<details>
+<summary>Solution</summary>
+
+<pre><code>function total(stream, value = 0) {
+ return () => {
+ const pair = stream()
+ return {
+ first: value + pair.first,
+ rest: total(pair.rest, value + pair.first),
+ }
+ }
+}
+</code></pre>
+</details>
+
+**Write `reduce` for streams.**
+
+`reduce` should take a 2-argument accumulator function, an initial value, and a stream.
+
+```js
+take(reduce((acc, value) => acc + value, 0, nums), 6)
+// => [1, 3, 6, 10, 15, 21]
+take(reduce((acc, value) => acc * value, 1, nums), 6)
+// => [1, 2, 6, 24, 120, 720]
+```
+
+_Hint: Generalize your solution for `total`._
+
+<details>
+<summary>Solution</summary>
+
+<pre><code>function reduce(fn, init, stream) {
+ return () => {
+ const pair = stream()
+ const newValue = fn(init, pair.first)
+ return {
+ first: newValue,
+ rest: reduce(fn, newValue, pair.rest),
+ }
+ }
+}
+</code></pre>
+</details>
+
+**Using `map` and `reduce`, create a stream of the [Fibonacci sequence](https://en.wikipedia.org/wiki/Fibonacci_number).**
+
+```js
+take(fib, 8)
+// => [0, 1, 1, 2, 3, 5, 8, 13]
+```
+
+_Hint #1: Your initial value should be the first **two** values of the sequence._
+
+_Hint #2: You do not need to use the input stream's values._
+
+<details>
+<summary>Solution</summary>
+
+<pre><code>function fib() {
+ return {
+ // `reduce` will consume the first
+ // 0, so we'll manually put it
+ // at the front
+ first: 0,
+ rest: map(
+ pair => pair[0],
+ reduce(
+ ([a, b], _) => [b, a + b],
+ [0, 1],
+ fixed('foobar')
+ )
+ )
+ }
+}
+</code></pre>
+</details><br>
+
+## 🎉🎉
+
+Using nothing but functions and some JavaScript objects, we were able to create and modify infinite sequences of data - and we did it all without making our computer cry!
+
+Thanks for reading this post, and an extra special thanks if you took the time to go through the exercises I put together after lots of editing. I really appreciate it. If not, you may want to try them out on a rainy day, I bet you'll learn something!
+
+It would be a mistake to leave you without some links for further reading.
+
+- [Structure and Interpretation of Computer Programs](https://mitpress.mit.edu/sites/default/files/sicp/full-text/book/book-Z-H-4.html#%_toc_start) is my favorite text on how to write and compose programs. Specifically, [Section 3.5](https://mitpress.mit.edu/sites/default/files/sicp/full-text/book/book-Z-H-24.html#%_sec_3.5) talk about streams that look eerily similar to the ones in this post. That's no accident!
+- My first introduction to this data structure was in Dan Grossman's [Programming Languages course on Coursera](https://www.coursera.org/learn/programming-languages). I had an absolute blast with this one, and learned a ton about FP, OOP, and type systems. I _highly_ recommend it.
+- Lastly, if you like the idea of infinite sequences but want to use more modern JS instead of first principles, [Reginald Braithwaite has written extensively on the topic](https://raganwald.com/2019/03/11/enumerations-denumerables-recursion-infinity.html).
+
+That's all I have for you now. Thanks again for reading, feel free to share on social media, and [come work with me at Stripe (we're hiring almost everywhere!)](https://stripe.com/jobs)
diff --git a/hidden/2019-05-18-katex-test.md b/hidden/2019-05-18-katex-test.md
new file mode 100644
index 0000000..feecee1
--- /dev/null
+++ b/hidden/2019-05-18-katex-test.md
@@ -0,0 +1,15 @@
+---
+title: KaTeX test
+date: 2019-05-18
+route: /katex-test
+hidden: true
+---
+
+Just testing server-side https://katex.org/.
+
+$$
+\begin{aligned}
+ z_0(n) &= n + 1 \\
+ z_0^n(n) &= z_1(n) = 2n
+\end{aligned}
+$$
diff --git a/hidden/2019-05-19-large-numbers.md b/hidden/2019-05-19-large-numbers.md
new file mode 100644
index 0000000..cb619e9
--- /dev/null
+++ b/hidden/2019-05-19-large-numbers.md
@@ -0,0 +1,469 @@
+---
+title: A Blueprint for Large Numbers
+route: /large-numbers
+date: 2019-05-19
+description: Let's generate numbers that can't fit in the observable universe.
+---
+
+Consider a modest function "A," which takes a number and adds one to it.
+
+$$
+\begin{aligned}
+ A(1) &= 2 \\
+ A(2) &= 3 \\
+ A(99) &= 100
+\end{aligned}
+$$
+
+It's not much of an operator, and is easily defeated by its counterpart: "evil A."
+
+$$
+\begin{aligned}
+ \check A(2) &= 1 \\
+ \check A(3) &= 2 \\
+ \check A(100) &= 99
+\end{aligned}
+$$
+
+We can apply "A" multiple times.
+
+$$
+\begin{aligned}
+ A(A(1)) = A(2) &= 3 \\
+ A(A(A(A(1)))) &= 5
+\end{aligned}
+$$
+
+Repeating "A" is a little tiring, so we can use a superscript to make things a little more concise.
+
+$$
+\begin{alignedat}{2}
+ A(A(1)) &= A^2(1) &&= 3 \\
+ A(A(A(A(10)))) &= A^4(10) &&= 14
+\end{alignedat}
+$$
+
+Since the superscript implies "add 1 this many times," the following properties unfold:
+
+$$
+\begin{aligned}
+A^n(1) &= 1 + n \\
+A^n(10) &= 10 + n \\
+A^n(99) &= 99 + n
+\end{aligned}
+$$
+
+Or, more generally:
+
+$$ A^n(m) = m + n $$
+
+Behold, **addition**!
+
+## Small steps
+
+Our goal is to make large numbers, so we may start throwing some of them at A in hopes of making them even bigger.
+
+$$
+\begin{aligned}
+A^{1000}(1000) &= 2000 \\
+A^{1000000}(1000000) &= 2000000
+\end{aligned}
+$$
+
+But we're not making much progress. For starters, we have to pass in _two_ big numbers - what a waste. Let's not repeat ourselves so much by creating a new function "B":
+
+$$ B(n) = A^n(n) $$
+
+Now we can pass large numbers into B just once - half the work (I checked!).
+
+$$
+\begin{aligned}
+B(1000) &= A^{1000}(1000) \\
+&= 1000 + 1000 \\
+&= 2000 \\
+B(1000000) &= 2000000 \\
+B(1000000000) &= 2000000000
+\end{aligned}
+$$
+
+We can see that the following property holds:
+
+$$ B(n) = A^n(n) = n + n = 2 \cdot n $$
+
+Behold, **multiplication!**
+
+Try as it might, "evil A" can't stop our new friend "B" from taking our numbers to great heights.
+
+$$
+\begin{aligned}
+\check A(n) &= n - 1 \\
+B(n) &= 2 \cdot n \\
+\check A(B(10)) &= 20 - 1 = 19 \\
+\check A(B(100)) &= 199 \\
+\check A(B(5000)) &= 9999 \\
+\end{aligned}
+$$
+
+But "evil B" can, stopping our numbers dead in their tracks.
+
+$$
+\begin{aligned}
+\check B(n) &= \frac{n}{2} \\
+B(n) &= 2 \cdot n \\
+\check B(B(10)) &= \frac{20}{2} = 10 \\
+\check B(B(100)) &= 100 \\
+\check B(B(5000)) &= 5000 \\
+\end{aligned}
+$$
+
+We'll need something stronger.
+
+## Slightly bigger steps
+
+We have a function "B" which _doubles_ a number.
+
+$$
+\begin{aligned}
+B(1) &= 2 \\
+B(99) &= 198 \\
+B(500) &= 1000
+\end{aligned}
+$$
+
+We can apply "B" multiple times.
+
+$$
+\begin{aligned}
+B(B(1)) = B(2) &= 4 \\
+B(B(B(10))) &= 80
+\end{aligned}
+$$
+
+To shorten things, we can use our trusty friend, the superscript.
+
+$$
+\begin{alignedat}{2}
+B(B(1)) &= B^2(1) &&= 4 \\
+B(B(B(10))) &= B^3(10) &&= 80
+\end{alignedat}
+$$
+
+This pattern may be a bit harder to spot, but we can handle it by expanding things a little.
+
+$$
+\begin{aligned}
+B^4(5) &= 2 \cdot B^3(5) \\
+&= 2 \cdot 2 \cdot B^2(5) \\
+&= 2 \cdot 2 \cdot 2 \cdot B(5) \\
+&= 2 \cdot 2 \cdot 2 \cdot 2 \cdot 5 \\
+&= 2^4 \cdot 5
+\end{aligned}
+$$
+
+The following pattern emerges:
+
+$$ B^n(5) = 2^n \cdot 5 $$
+
+Or, more generally:
+
+$$ B^n(m) = 2^n \cdot m $$
+
+Behold, **exponentiation!**
+
+Just as before, we'll consolidate m and n by introducing a new function "C."
+
+$$ C(n) = B^n(n) = 2^n \cdot n $$
+
+## Getting there
+
+Our numbers are starting to grow pretty quickly:
+
+$$
+\begin{alignedat}{2}
+C(3) &= 2^3 \cdot 3 &&= 24 \\
+C(10) &= 2^{10} \cdot 10 &&= 10240 \\
+C(100) &= 2^{100} \cdot 100 &&= 12676506\mathellipsis
+\end{alignedat}
+$$
+
+Passing 100 to our friend "C" produces `126765060022822940149670320537600`, which is a whopping 33 digits in length. Switch the 100 to 1000 and we get:
+
+<pre><code>107150860718626732094842504906000
+181056140481170553360744375038837
+035105112493612249319837881569585
+812759467291755314682518714528569
+231404359845775746985748039345677
+748242309854210746050623711418779
+541821530464749835819412673987675
+591655439460770629145711964776865
+421676604298316526243868372056680
+69376000
+</code></pre>
+
+Coming in at 305 digits. Pretty huge - larger than [the number of particles in the universe](https://en.wikipedia.org/wiki/Elementary_particle#Common_elementary_particles) - but still easily represented here in this article.
+
+Additionally, our "evil B" from earlier is no match for us now.
+
+$$
+\begin{aligned}
+\check B(n) &= \frac{n}{2} \\
+C(n) &= 2^n \cdot n \\
+\check B(C(10)) &= 5120 \\
+\check B(C(100)) &= 633825\mathellipsis \text{(32 digits)} \\
+\check B(C(1000)) &= 535754\mathellipsis \text{(304 digits)}\\
+\end{aligned}
+$$
+
+Try as it might, "evil B" can only occasionally knock a single digit off our numbers.
+
+A new challenger approaches, however, that can take them out using its secret weapon - [the logarithm](https://www.khanacademy.org/math/algebra2/exponential-and-logarithmic-functions/introduction-to-logarithms/a/intro-to-logarithms).
+
+$$
+\begin{aligned}
+\check C(n) &= \lg(n) \\
+C(n) &= 2^n \cdot n \\
+\check C(C(10)) &= 13.321\mathellipsis \\
+\check C(C(100)) &= 106.643\mathellipsis \\
+\check C(C(1000)) &= 1009.965\mathellipsis \\
+\end{aligned}
+$$
+
+Our numbers are _barely_ able to escape the mighty logarithm. We'll need something stronger.
+
+## To new heights
+
+We have a function "C" which raises 2 to the power "n" and multiplies it by "n"
+
+$$
+\begin{alignedat}{2}
+C(n) &= 2^n \cdot n \\
+C(3) &= 2^3 \cdot 3 &&= 24 \\
+C(10) &= 2^{10} \cdot 10 &&= 10240 \\
+\end{alignedat}
+$$
+
+This function has a little sibling which does the same thing, but does not multiply by "n". It won't produce numbers quite as big, but will be a little easier for us to work with.
+
+$$
+\begin{aligned}
+\dot C(n) &= 2^n \\
+\dot C(3) &= 8 \\
+\dot C(10) &= 1024
+\end{aligned}
+$$
+
+We can apply "little C" multiple times.
+
+$$
+\begin{alignedat}{2}
+\dot C(n) &= 2^n \\
+\dot C(\dot C(n)) &= 2^{\dot C(n)} &&= 2^{2^n} \\
+\dot C(\dot C(\dot C(n))) &= 2^{\dot C(\dot C(n))} \\
+&= 2^{2^{\dot C(n)}} &&= 2^{2^{2^{n}}}
+\end{alignedat}
+$$
+
+Just as before, we can use our trusty friend the superscript.
+
+$$\dot C^5(n) = 2^{2^{2^{2^{2^n}}}}$$
+
+Repeated applications of "little C" result in these fairly alien "towers" of 2s. In particular:
+
+$$\dot C^m(n) = \underbrace{2^{2^{2^{\cdot^{\cdot^{\cdot^{2^n}}}}}}}_{\text{m copies of 2}}$$
+
+Once again we can consolidate m and n:
+
+$$D(n) = \dot C^n(n) = \underbrace{2^{2^{2^{\cdot^{\cdot^{\cdot^{2^n}}}}}}}_{\text{n copies of 2}}$$
+
+Just as we removed the trailing end for "C", we'll remove the trailing n for "D" to simplify things.
+
+$$
+\begin{aligned}
+C(n) &= 2^n \cdot n \\
+\dot C(n) &= 2^n \\
+D(n) &= \underbrace{2^{2^{2^{\cdot^{\cdot^{\cdot^{2^n}}}}}}}_{\text{n copies of 2}} \\
+\dot D(n) &= \underbrace{2^{2^{2^{\cdot^{\cdot^{\cdot^{2}}}}}}}_{\text{n copies of 2}}
+\end{aligned}
+$$
+
+These "towers of exponents" are referred to as "[tetrations](https://en.wikipedia.org/wiki/Tetration)" and are typically represented using [Knuth's up-arrow notation](https://en.wikipedia.org/wiki/Knuth%27s_up-arrow_notation).
+
+$$
+\dot D(5) = 2^{2^{2^{2^2}}} = 2 \uparrow \uparrow 5
+$$
+
+They're bizarre-looking, and grow...**really quickly**. How quickly?
+
+$$
+\begin{alignedat}{2}
+\dot D(2) &= 2^2 &&= 4 \\
+\dot D(3) &= 2^{2^2} &&= 16 \\
+\dot D(4) &= 2^{2^{2^2}} &&= 65536 \\
+\dot D(5) &= 2^{2^{2^{2^2}}} &&= 2^{65536}
+\end{alignedat}
+$$
+
+At just 5 items in, we're produced a number that is 19,729 digits in length. Much larger than anything we've seen so far, but still _tangible_. In fact, I can print this number at size 11 font with 1" margins in just 7 sheets of paper (about 3,000 digits per page).
+
+![a print preview showing the very long number (20035299...) with "Total: 7 pages"](/img/digits.png)
+
+Why stop there?
+
+$$\dot D(6) = 2^{2^{2^{2^{2^2}}}} = \text{???}$$
+
+What _is_ this number? Concretely, it's 2 raised to the giant number we just saw. How much larger does that make it?
+
+While we could print `D(5)` on 7 pages of paper, we'll need 7 pages of paper **just to print the number of digits in `D(6)`**. What if we wanted to print `D(6)` itself? We'll need about `D(5)` pages.
+
+More specifically, we'll need `D(5) / 3,000` pages, but `D(5)` is so massive the `/ 3,000` does absolutely nothing to it.
+
+How about `D(7)`? We'll need `D(5) / 3,000` pages to print its digits, and `D(6) / 3,000` pages to print the number itself. There's a pattern here, but not one that translates well into physical sheets of paper. Simply put, the number's big.
+
+In fact, our function generates numbers so big that "evil C" is quickly drowned by them.
+
+$$
+\begin{alignedat}{2}
+\check C(n) &= \lg(n) \\
+\check C(\dot D(2)) &= \lg{(2^2)} &&= 2 \\
+\check C(\dot D(3)) &= \lg{(2^{2^2})} &&= 2^2 \\
+\check C(\dot D(4)) &= \lg{(2^{2^{2^2}})} &&= 2^{2^2} \\
+\check C(\dot D(5)) &= \lg{(2^{2^{2^{2^2}}})} &&= 2^{2^{2^2}} \\
+\check C(\dot D(6)) &= \lg{(2^{2^{2^{2^{2^2}}}})} &&= 2^{2^{2^{2^2}}}
+\end{alignedat}
+$$
+
+Try as it might, "evil C" can only knock off a single item from our ever-growing tower of exponents.
+
+A tower of exponents is a real force to be reckoned with.
+
+## But not big enough
+
+We have a function "D" which generates a tower of 2s with a height of "n."
+
+$$
+\dot D(n) = \underbrace{2^{2^{2^{\cdot^{\cdot^{\cdot^{2}}}}}}}_{\text{n copies of 2}}
+$$
+
+We can apply this function to itself.
+
+$$
+\begin{aligned}
+\dot D(\dot D(n)) &= \dot D(\underbrace{2^{2^{2^{\cdot^{\cdot^{\cdot^{2}}}}}}}_{\text{n copies of 2}}) \\
+&= \underbrace{2^{2^{2^{\cdot^{\cdot^{\cdot^{2}}}}}}}_{(\underbrace{2^{2^{2^{\cdot^{\cdot^{\cdot^{2}}}}}}}_{\text{n copies of 2}})\text{ copies of 2}}
+\end{aligned}
+$$
+
+In the previous section, we saw how `D(n-1)` dictated the **number of digits** for `D(n)`. Well, `D(D(n))` grows not on the level of _exponents_, but on the level of the _number of exponents in the tower_.
+
+**While `D(6)` was practically impossible to imagine, `D(D(6))` is pure nightmare fuel. Let's continue.**
+
+Just as earlier, we can use a superscript to simplify things:
+
+$$
+\left.
+\dot D^m(n) = \underbrace{2^{2^{2^{\cdot^{\cdot^{\cdot^{2}}}}}}}_{(\underbrace{2^{2^{2^{\cdot^{\cdot^{\cdot^{2}}}}}}}_{(\cdot^{\cdot^{\cdot}}) \text{ copies of 2}})\text{ copies of 2}}
+\right\} \text{m times}
+$$
+
+And we can consolidate m and n like so:
+
+$$
+\begin{aligned}
+E(n) &= \dot D^n(n) \\
+&= \left.
+ \underbrace{2^{2^{2^{\cdot^{\cdot^{\cdot^{2}}}}}}}_{(\underbrace{2^{2^{2^{\cdot^{\cdot^{\cdot^{2}}}}}}}_{(\cdot^{\cdot^{\cdot}}) \text{ copies of 2}})\text{ copies of 2}}
+ \right\} \text{n times}
+\end{aligned}
+$$
+
+These towers-of-towers can be hard to read (and harder to write), so we can simplify things a bit using [our previous up-arrow notation](https://en.wikipedia.org/wiki/Knuth%27s_up-arrow_notation).
+
+$$
+\begin{aligned}
+\dot D(n) &= 2 \uparrow \uparrow n \\
+\dot D(\dot D(n)) &= 2 \uparrow \uparrow (2 \uparrow \uparrow n) \\
+\dot D(\dot D(\dot D(n))) &= 2 \uparrow \uparrow (2 \uparrow \uparrow (2 \uparrow \uparrow n)) \\
+\dot D^m(n) &= \underbrace{\dot D(\dot D( \cdots \dot D(n)))}_{\text{m copies of } \dot D} \\
+&= \underbrace{2 \uparrow \uparrow (\cdots (2 \uparrow \uparrow n))}_{\text{m copies of 2}}
+\end{aligned}
+$$
+
+(and consolidate m and n):
+
+$$
+\begin{aligned}
+E(n) &= \dot D^n(n) \\
+&= \underbrace{2 \uparrow \uparrow (2 \uparrow \uparrow (\cdots (2 \uparrow \uparrow n))}_{\text{n copies of 2}}
+\end{aligned}
+$$
+
+Lastly, we'll want to get rid of that pesky "n" at the end and create a "little E" just as we did for "C" and "D".
+
+$$
+\begin{aligned}
+D(n) &= \underbrace{2^{2^{2^{\cdot^{\cdot^{\cdot^{2^n}}}}}}}_{\text{n copies of 2}} \\
+\dot D(n) &= \underbrace{2^{2^{2^{\cdot^{\cdot^{\cdot^{2}}}}}}}_{\text{n copies of 2}} \\
+E(n) &= \underbrace{2 \uparrow \uparrow (2 \uparrow \uparrow (\cdots (2 \uparrow \uparrow n))}_{\text{n copies of 2}} \\
+\dot E(n) &= \underbrace{2 \uparrow \uparrow (2 \uparrow \uparrow (\cdots (2 \uparrow \uparrow 2))}_{\text{n copies of 2}}
+\end{aligned}
+$$
+
+Conveniently, mathematicians have an elegant way to represent "apply double-up-arrow n times," and all it takes is adding a third arrow.
+
+$$
+\begin{aligned}
+\dot E(n) &= \underbrace{2 \uparrow \uparrow (2 \uparrow \uparrow (\cdots (2 \uparrow \uparrow 2))}_{\text{n copies of 2}} \\
+\dot E(n) &= 2 \uparrow \uparrow \uparrow n
+\end{aligned}
+$$
+
+## Arrows, arrows, arrows
+
+In fact, if we repeat the process we used to generate "B" through "E":
+
+$$
+\begin{aligned}
+E^m(n) &= \underbrace{E(E(\cdots(E(n))}_{\text{m times}} \\
+E^n(n) &= \underbrace{E(E(\cdots(E(n))}_{\text{n times}} \\
+F(n) &= E^n(n) \\
+G(n) &= F^n(n) \\
+H(n) &= G^n(n) \\
+\dots
+\end{aligned}
+$$
+
+All we're really doing is **adding more arrows**.
+
+$$
+\begin{aligned}
+E(n) &= 2 \uparrow \uparrow \uparrow n \\
+F(n) &= 2 \uparrow \uparrow \uparrow \uparrow n \\
+G(n) &= 2 \uparrow \uparrow \uparrow \uparrow \uparrow n \\
+H(n) &= 2 \uparrow \uparrow \uparrow \uparrow \uparrow \uparrow n \\
+\dots
+\end{aligned}
+$$
+
+Eventually the arrows themselves will become unweildy:
+
+$$
+Z(n) = \underbrace{2 \uparrow \uparrow \cdots \uparrow n}_\text{25 arrows}
+$$
+
+Worry not, for we can provide a superscript _to the arrow_ itself. (Keep in mind that our numbers became _incomprehensibly_ large 23 arrows ago.)
+
+$$
+\begin{aligned}
+Z(n) &= \underbrace{2 \uparrow \uparrow \cdots \uparrow n}_\text{25 arrows} \\
+Z(n) &= 2 \uparrow^{25} n
+\end{aligned}
+$$
+
+So, what if we wanted to continue growing the number of arrows?
+
+<div style="padding: 8px; background: #e0e0e0; color: red">[Existential Error] Shutting down...
+</div>
+
+I'll leave that to you, dear reader, for I am exhausted.
+
+Thanks for reading :) If you enjoyed this article, you'll probably love [Graham's Number](https://www.youtube.com/watch?v=GuigptwlVHo) and [TREE(3)](https://www.youtube.com/watch?v=3P6DWAwwViU).
diff --git a/hidden/2020-04-01-j-sorting.md b/hidden/2020-04-01-j-sorting.md
new file mode 100644
index 0000000..2bc0bae
--- /dev/null
+++ b/hidden/2020-04-01-j-sorting.md
@@ -0,0 +1,173 @@
+---
+title: Hello, J! Sorting Some Strings
+route: /j-sorting
+date: 2020-04-01
+description: We'll learn a few fun features in the J programming language by sorting some strings.
+---
+
+<!-- Inconsolata monospace doesn't work too good with J's unicode boxes -->
+<style>pre code {font-family:monospace}</style>
+
+Following my previous [(1)](/j-fibonacci) posts [(2)](/j-pascal) on a fun little language called J (http://jsoftware.com/), I thought I'd do a quick writeup for some source code I provided in the [Khan Academy Alumni](https://www.khanacademy.org/about/alumni) slack channel.
+
+([They're](https://www.khanacademy.org/careers) hiring! [Stripe](https://stripe.com/jobs) is too!)
+
+Over the next few paragraphs we're going to sort some strings by length, and end up with a function that is 5 characters in length: `\:#&>` (really!)
+
+```
+ (\:#&>) ;: 'sorting strings by length'
+┌───────┬───────┬──────┬──┐
+│sorting│strings│length│by│
+└───────┴───────┴──────┴──┘
+```
+
+## Boxes of strings
+
+We can use the [`;: Words`](https://www.jsoftware.com/docs/help804/dictionary/d332.htm) verb to get a list of words in a string.
+
+```
+ 'a'; 'tall'; 'cat'
+┌─┬────┬───┐
+│a│tall│cat│
+└─┴────┴───┘
+ ;: 'a tall cat'
+┌─┬────┬───┐
+│a│tall│cat│
+└─┴────┴───┘
+```
+
+The fancy characters around each word represent the fact that they are "boxed."
+
+We can [`> Open`](https://www.jsoftware.com/docs/help804/dictionary/d020.htm) boxes.
+
+```
+ > ;: 'a tall cat'
+a
+tall
+cat
+```
+
+We can get the number of items in a list (or a string - which is a list of characters) using the [`# Tally`](https://www.jsoftware.com/docs/help804/dictionary/d400.htm) verb.
+
+```
+ # 1 3 5
+3
+ # 'hello'
+5
+```
+
+We can compose these two operations with [`& Bond`](https://www.jsoftware.com/docs/help804/dictionary/d630n.htm) conjunction, which takes two verbs and returns a single verb.
+
+```
+ # & > ;: 'hello world'
+5 5
+ #&> ;: 'a tall cat'
+1 4 3
+```
+
+Now we are able to get the number of characters in each string.
+
+## Sorting and ranking
+
+[`\: Sort Down`](https://www.jsoftware.com/docs/help804/dictionary/d432.htm) is a powerful verb which sorts the left argument using the right argument for scoring (in descending order).
+
+For example, we can rearrange "hello" by giving `l` and `h` a score of 1 and 2 respectively, and giving `e` and `o` a score of 11 and 10 (respectively). `e` will then appear at the front of the result as it received the highest score.
+
+```
+ 'hello' \: 1 11 2 2 10
+eollh
+```
+
+We can use this to sort our list of words, using the length of each word as its score.
+
+```
+ #&> ;: 'a tall cat'
+1 4 3
+ (;: 'a tall cat') \: 1 4 3
+┌────┬───┬─┐
+│tall│cat│a│
+└────┴───┴─┘
+ (;: 'a tall cat') \: #&> ;: 'a tall cat'
+┌────┬───┬─┐
+│tall│cat│a│
+└────┴───┴─┘
+```
+
+## Making things shorter
+
+We've successfully used the `;:`, `\:`, `#`, `&`, and `>` operators to piece together a working sort mechanism, but there's one flaw - we have to repeat `;: 'a tall cat'` twice 😱
+
+To alleviate this, we can use one of J's strongest features, [hooks](https://www.jsoftware.com/docs/help804/primer/hook.htm).
+
+In the following example, we'll take a list of numbers and add 7% to each of them. We may be inclined to write it like so:
+
+```
+ 10 40 55 + 0.07&* 10 40 55
+10.7 42.8 58.85
+```
+
+Which reads, take the list `10 40 55` and apply `0.07&*` (remember [bond](https://www.jsoftware.com/docs/help804/dictionary/d630n.htm)?) to each item. Add the result to the list `10 40 55`.
+
+(Author's note: we also may be inclined to just multiply by 1.07 but that requires math).
+
+Hooks allow us to simplify this by simply leaving off one of the arguments.
+
+```
+ (+ 0.07&*) 10 40 55
+10.7 42.8 58.85
+```
+
+How does this work? J recognizes that we have two verbs within our parentheses (`+` and `0.07&*`) and combines them into a "[hook](https://www.jsoftware.com/docs/help804/primer/hook.htm)." When we place a single argument at the end, J **automatically applies it to both sides of the hook**.
+
+Put another way: `(f g) x <=> x f g x` for any two verbs `f` and `g` (don't forget your parentheses!)
+
+So, the following expressions are equivalent:
+
+```
+ 10 40 55 + 0.07&* 10 40 55
+10.7 42.8 58.85
+ (+ 0.07&*) 10 40 55
+10.7 42.8 58.85
+```
+
+Let's circle back to our sorting mechanism before and see where we can apply this.
+
+```
+ (;: 'a tall cat') \: #&> ;: 'a tall cat'
+┌────┬───┬─┐
+│tall│cat│a│
+└────┴───┴─┘
+```
+
+Spot it? Between our two instances of `;: 'a tall cat'` we have two verbs: `\:` and `#&>`. Let's hook 'em.
+
+```
+ (\: #&>) ;: 'a tall cat'
+┌────┬───┬─┐
+│tall│cat│a│
+└────┴───┴─┘
+```
+
+Lastly, we can eliminate some whitespace and build our custom verb.
+
+```
+ sort =. \:#&>
+ sort ;: 'a tall cat'
+┌────┬───┬─┐
+│tall│cat│a│
+└────┴───┴─┘
+```
+
+## We did it!
+
+I hope this was a decently short and pleasant introduction into some fun features of the [J programming language](https://www.jsoftware.com/#/README):
+
+- [`& Bond`](https://www.jsoftware.com/docs/help804/dictionary/d630n.htm)
+- [`\: Sort Down`](https://www.jsoftware.com/docs/help804/dictionary/d432.htm)
+- [Hooks](https://www.jsoftware.com/docs/help804/primer/hook.htm)
+
+I encourage you to download J and play around with it. Hooks and forks and [other trains](https://www.jsoftware.com/docs/help804/dictionary/dictf.htm) pop up in all sorts of places and may make you think differently about the code you write everyday.
+
+Also consider [following me on twitter](https://twitter.com/jdan).
+
+Thanks for reading ✨
diff --git a/hidden/2020-04-28-98-dot-css.md b/hidden/2020-04-28-98-dot-css.md
new file mode 100644
index 0000000..c5e501a
--- /dev/null
+++ b/hidden/2020-04-28-98-dot-css.md
@@ -0,0 +1,72 @@
+---
+title: 98.css
+date: 2020-04-28
+route: /98-dot-css
+description: A few days ago I launched 98.css, a design system for making interfaces that look like Windows 98. I'm pretty proud with how it turned out.
+---
+
+A few days ago [I launched 98.css](https://jdan.github.io/98.css/), a "design system" (it's a CSS file) for making
+interfaces that look like Windows 98 ([on GitHub](https://github.com/jdan/98.css)). I'm pretty proud with how it turned out.
+
+!["a screenshot of a window with the title 'My First VB4 Program' and two buttons OK and Cancel, styled like a Windows 98 dialog"](/img/window.png)
+
+I actually started exploring this idea two years ago in [a small codepen](https://codepen.io/jdan/pen/QmXYPB) where I had a go at
+styling some buttons. This was born out of a remarkably pedantic observation I made where many people would draw Windows-98 buttons like this:
+
+<div style="display: inline-block; padding: 20px; background: silver">
+<div style="width: 192px; height: 72px; border: 4px solid grey; border-top-color: white; border-left-color: white"></div>
+</div>
+
+Instead of with proper "pixelated" borders like so:
+
+<div style="display: inline-block; padding: 20px; background: silver">
+<div style="width: 200px; height: 80px; box-shadow: inset -4px -4px #0a0a0a, inset 4px 4px #fff, inset -8px -8px grey, inset 8px 8px #dfdfdf"></div>
+</div>
+
+Turns out this behavior is well-specified in the glorious [Microsoft Windows User Experience](https://amzn.to/2YdvLve) reference manual.
+I grabbed a copy and went to work. First buttons, then windows, navbars, scrollbars, inputs, and all sorts of things.
+
+<img alt="a magnified view showing pixel-perfect borders on a scrollbar and button element" src="https://github.com/jdan/98.css/blob/main/docs/zoom.png?raw=true?raw=true">
+
+My tools were a Windows 98 VM and MSPaint for measuring pixels. I used [Figma](https://www.figma.com/) to draw some icons (wonderful tool btw, great work team).
+I did all of my development on my Windows desktop using [Visual Studio Code](https://code.visualstudio.com/) and
+[Windows Subsystem for Linux (WSL)](https://docs.microsoft.com/en-us/windows/wsl/install-win10). Web development on Windows has come a long way and I really
+love my environment.
+
+The project itself had three main objectives:
+
+**Make it as pixel-perfect as I reasonably can** as a creative exercise. I'm going for nostalgia points and I have to really nail it.
+
+**Make it accessible**. Windows 98 is such a wonderfully accessible and intuitive UI style and I couldn't ruin that. I had to do right by it.
+
+**Make it a stylesheet that prettied up HTML and nothing more**. There are some obvious UI elements that I could build with JavaScript:
+nav menus, tab panels, that sort of thing, but I was incredibly hesitant to pick a JavaScript framework or roll my own. Instead I made
+the decision to style HTML and not care who sent it to me, be it React or Vue or whatever you want.
+
+So after about two weeks (with lots and lots of breaks, I'm just not good at sitting in front of a computer for hours anymore) of
+development and cutting scope, I shipped it. I was surprised by how many people "got" it, how many people felt the same nostalgia
+that I felt when I first set out to build this silly thing. The haunted orange website even had its
+[fair share of pleasant comments](https://news.ycombinator.com/item?id=22940564):
+
+> My heart beat a little faster when I saw the VB dialog box. I experienced building software for the first time when I was taken to my father's office where they used VB and Foxpro (and still do) to build ERP systems. Everything looked like this site.
+>
+> The waves of nostalgia are sure coming on hard. Thank you to the author of this. This whole site is a work of art!
+
+The community also sent [many PRs my way](https://github.com/jdan/98.css/pulls?q=is%3Apr+is%3Aclosed), from small bug-fixes to [giving 98.css properly-pixelated fonts](https://github.com/jdan/98.css/pull/27) to [semantically expandable tree views](https://github.com/jdan/98.css/pull/69). Y'all are the best ✨.
+
+I also found myself distracted for a few hours [building a service](https://github.com/jdan/98.css-badges) to generate these swanky npm version and bundle size badges, since the ones on the wonderful [shields.io](https://shields.io) didn't fit my aesthetic.
+
+<a href="http://npm.im/98.css"><img alt="npm" src="https://98badges.now.sh/api/version" style="border: none; width: auto; height: 32px"></a> <a href="https://unpkg.com/98.css"><img alt="gzip size" src="https://98badges.now.sh/api/size" style="border: none; width: auto; height: 32px"></a>
+
+The code for generating these on-demand [lives here and is quite terrible](https://github.com/jdan/98.css-badges/blob/main/api/size.js), but I'm
+proud of it for working at all.
+
+I was fortunate enough to lose myself in [a clicker game built with 98.css](https://twitter.com/pieskucom/status/1254396566863036418)
+called "Time is Money" for about an hour, and spent some time clicking around [the GitHub projects that brought in 98.css](https://github.com/jdan/98.css/network/dependents?package_id=UGFja2FnZS0xMTA3MTQ5NTgw).
+
+If you made anything cool, please send it my way, nothing would make me happier :)
+
+All in all I smiled while building this, and brought some smiles when I shipped it. That makes for a good project in my book, but only
+time will tell if this library is actually useful to anyone or just something people tweet about and move on.
+
+I'm peaceful with either outcome. ✌️
diff --git a/hidden/2020-05-05-mandel.md b/hidden/2020-05-05-mandel.md
new file mode 100644
index 0000000..23dc821
--- /dev/null
+++ b/hidden/2020-05-05-mandel.md
@@ -0,0 +1,107 @@
+---
+title: ASCII Mandelbrot Set
+route: /mandelbrot
+date: 2020-05-05
+description:
+---
+
+This is rendering of the [Mandelbrot set](https://en.wikipedia.org/wiki/Mandelbrot_set) in ASCII,
+using HTML anchor tags and the address bar.
+
+Click around to zoom in on different parts of the fractal, and use your browser history to zoom out.
+
+<div id="mandel"></div>
+
+<div style="text-align: right">
+ <a href="#-1/2,0,7/2">Reset</a>
+</div>
+
+You'll hit a wall pretty quickly due to the precision of numbers in JavaScript. [Fraction.js](https://github.com/infusion/Fraction.js/) gets us pretty close, though.
+
+Feel free to poke around [on GitHub](https://github.com/jdan/thatjdanisso.cool/blob/main/articles/2020-05-05-mandel.md) if you have any ideas for improving this.
+
+<style>
+ #mandel {
+ line-height: 1;
+ }
+
+ @media (max-width: 480px) {
+ #mandel {
+ overflow-x: scroll;
+ }
+ }
+
+ #mandel a:visited {
+ color: green;
+ }
+</style>
+
+<script src="/js/fraction.min.js"></script>
+
+<script>
+ const WIDTH = 59;
+ const HEIGHT = 30;
+
+ const mandelChar = (a, b) => {
+ let z_a = new Fraction(0);
+ let z_b = new Fraction(0);
+
+ let i = 0;
+
+ while (i++ < 20) {
+ /* z = z**2 + c */
+ // const _z_a = z_a*z_a - z_b * z_b + a;
+ const _z_a = z_a.mul(z_a).sub(z_b.mul(z_b)).add(a);
+ //const _z_b = 2 * z_a * z_b + b;
+ const _z_b = z_a.mul(z_b).mul(2).add(b);
+
+ z_a = _z_a;
+ z_b = _z_b;
+
+ //if (Math.sqrt(z_a * z_a + z_b * z_b) > 2) {
+ if (z_a.mul(z_a).add(z_b.mul(z_b)).compare(4) > 0) {
+ break;
+ }
+ }
+
+ return ".:+x%#@"[Math.floor((i / 21) * 6)];
+};
+
+const draw = () => {
+ const $el = document.getElementById("mandel")
+
+$el.innerHTML = "";
+const [r, i, zoom] = document.location.hash
+.slice(1)
+.split(",")
+.map(i => new Fraction(i));
+
+ for (let j = 0; j < HEIGHT; j++) {
+ // const y = (j / HEIGHT) * zoom + i - zoom / 2;
+ const y = zoom.mul(j, HEIGHT).add(i).sub(zoom, 2);
+
+ const row = document.createElement("div");
+
+ for (let k = 0; k < WIDTH; k++) {
+ //const x = (k / WIDTH) * zoom + r - zoom / 2;
+ const x = zoom.mul(k, WIDTH).add(r).sub(zoom, 2)
+ const item = document.createElement("a");
+
+ const href = [x, y, zoom.mul(2, 3)].map(f => f.toFraction()).join(',')
+ item.setAttribute("href", `#${href}`);
+ item.innerHTML = mandelChar(x, y);
+ row.appendChild(item);
+ }
+
+ $el.appendChild(row);
+ }
+};
+
+window.addEventListener("hashchange", draw);
+
+if (!/#[\d\-\/]+$/.test(document.location.hash)) {
+document.location.hash = "#-1/2,0,7/2";
+}
+
+draw();
+</script>
diff --git a/hidden/2020-05-22-functions-that-go-backwards.md b/hidden/2020-05-22-functions-that-go-backwards.md
new file mode 100644
index 0000000..cebd1f1
--- /dev/null
+++ b/hidden/2020-05-22-functions-that-go-backwards.md
@@ -0,0 +1,314 @@
+---
+title: Functions That Go Backwards
+route: /functions-that-go-backwards
+date: 2020-05-22
+description: A gentle introduction to logic programming with Prolog, exploring how to define programs in terms of relations instead of input and output.
+---
+
+In most programming languages, we may author a function that takes an **input** and produces some **output**.
+
+```js
+console.log(getTrafficLightColor("green", "wait"))
+// => "yellow"
+```
+
+We can say that our code answers the following question: **Given a light color and an action, what happens to the color of the light?**
+
+And it answers it well! We can test its behavior, give it some types, whatever we want - to get a solid answer. We can compose these questions to ask more interesting ones:
+
+**If I wait twice at a green light, what will the color of the light be?**
+
+```js
+console.log(
+ getTrafficLightColor(
+ getTrafficLightColor("green", "wait"),
+ "wait"
+ )
+) // => "red"
+```
+
+But if I asked another question: **What color should the light be such that if I wait, it will turn red?**
+
+We're no longer asking a question in terms of its **input** (which is easy!), but its **output** (which makes us scratch our heads).
+
+## Logically speaking
+
+We'll use a fun "logic programming" language called [Prolog](https://en.wikipedia.org/wiki/Prolog) (you can grab [SWI-Prolog](https://www.swi-prolog.org/) for free) to explore this concept.
+
+Instead of writing a function in terms of its inputs, we write _relationships_ between input and output. An example of such a relation is **a valid transition for our traffic light turns `green` with a `wait` action into `yellow`**.
+
+All we care about are the `green`, `wait`, and `yellow` (these are known as "atoms" in Prolog), and we can use them to build a "**fact**".
+
+```prolog
+transition(green, wait, yellow).
+```
+
+We can choose whatever order we want as long as we're consistent. We'll go with the one above since it matches the prose we translated into Prolog.
+
+Let's write a few more of these in a file called `fsm.prolog`.
+
+```prolog
+% fsm.prolog
+transition(green, wait, yellow).
+transition(yellow, wait, red).
+transition(red, wait, green).
+```
+
+We can then load up `swipl` in our terminal and test it out. After we load our facts with `[fsm].` we can begin "querying" them.
+
+```prolog
+?- [fsm]. % load fsm.prolog
+?- transition(green, wait, yellow).
+true.
+?- transition(red, wait, yellow).
+false.
+```
+
+Our first query `transition(green, wait, yellow).` is a fact, because we defined it as such on the first line of `fsm.prolog`. Prolog tells us "true" - the thing we asked is `true`.
+
+The second query, `transition(red, wait, yellow)` does not appear in our database, so it is `false`.
+
+Of course, Prolog can do much more than recall facts that we already entered! The magic happens when we query with variables.
+
+```prolog
+?- transition(green, wait, Color).
+Color = yellow.
+```
+
+By using a variable (we just need to begin a word with an uppercase letter), we're now asking Prolog to fill in the blank for us through a process called "**unification**." Which `Color` does `transition(green, wait, Color)` result in a fact? `yellow`!
+
+Better yet, we can put this variable **wherever we want**. Our original question: **What color should the light be such that if I wait, it will turn red?** can be queried like so.
+
+```prolog
+?- transition(X, wait, red).
+X = yellow.
+```
+
+Because transition does not work on input and output, we can swap 'em as we please.
+
+```prolog
+?- transition(X, wait, purple).
+false.
+```
+
+Rather unfortunately, the traffic light we invented can never turn purple.
+
+How about representing *multiple* transitions? We can join queries with a comma to ask that Prolog fills in the blanks to satisfy both. Let's start at `green` and `wait` twice.
+
+```prolog
+?- transition(green, wait, State1),
+| transition(State1, wait, Final).
+State1 = yellow,
+Final = red.
+```
+
+We left two blanks, `State1` and `Final`, and Prolog filled em both to find that **waiting twice at a green light results in a red light**. Of course this works backwards for free.
+
+```prolog
+?- transition(Start, wait, State1),
+| transition(State1, wait, yellow).
+Start = red,
+State1 = green.
+```
+
+Want to wait twice for a yellow? You'll start at red.
+
+## Improving relations
+
+Just as we can define functions and compose them in ordinary programming languages, we can build "**rules**" out of facts and variables. For example, if we wanted an easier way to query for "waiting twice":
+
+```prolog
+% fsm.prolog
+wait_twice(Start, End) :-
+ transition(Start, wait, Middle),
+ transition(Middle, wait, End).
+```
+
+We've defined `wait_twice` such that we'll `transition` once from `Start` to `Middle`, then `Middle` to `End`. We can use it like so:
+
+```prolog
+?- [fsm].
+?- wait_twice(green, red).
+true.
+?- wait_twice(green, yellow).
+false.
+```
+
+Waiting twice at a green does in fact give us a red light, while it is _not true_ that waiting twice at a green light gives us a yellow light. Similar to before, we can query with variables:
+
+```prolog
+?- wait_twice(green, X).
+X = red.
+?- wait_twice(X, yellow).
+X = red.
+?- wait_twice(X, purple).
+false.
+```
+
+Let's dive into something a little more complicated, but much more rewarding. How about a rule that relates a start and end light color with a **list** of actions?
+
+We'll call this rule `transition_multi` and define it recursively - starting with an empty list. If we have an initial `State` and an empty list of actions `[]`, what should our final state be? Right where we started.
+
+```prolog
+% fsm.prolog
+transition_multi(State, [], State).
+```
+
+For the recursive step it may help to see how Prolog's "unification" works with lists.
+
+```prolog
+?- A = [1, 2, 3, 4].
+A = [1, 2, 3, 4].
+?- [A, B, C] = [1, 2, 3].
+A = 1,
+B = 2,
+C = 3.
+?- [A, B] = [1, 2, 3].
+false.
+```
+
+We can set a list to a variable, or pattern-match on `[Var1, Var2, Etc...]` if the lengths are the same.
+
+If we want to match the "rest" of the list, we can use the `|` operator.
+
+```prolog
+?- [A | Rest] = [1, 2, 3, 4].
+A = 1,
+Rest = [2, 3, 4].
+?- [A, B | Rest] = [1, 2].
+A = 1,
+B = 2,
+Rest = [].
+```
+
+So, back to `transition_multi`. Our definition looks pretty similar to `wait_twice`.
+
+```prolog
+% fsm.prolog
+transition_multi(State, [], State).
+transition_multi(State, [Action | Rest], End) :-
+ transition(State, Action, Middle),
+ transition_multi(Middle, Rest, End).
+```
+
+We unify the list of actions into `Action` and `Rest`, then transition from `State` to `Middle` before recursively `transition_multi`'ing from `Middle` to `End`.
+
+Let's try it out.
+
+```prolog
+?- [fsm].
+?- transition_multi(green, [wait, wait], red).
+true
+```
+
+Groovy, so Prolog can confirm that `wait`ing twice at a green light gets us a red light. We can put a few variables in to flex a bit.
+
+```prolog
+?- transition_multi(yellow, [wait, wait, wait], X).
+X = yellow
+?- transition_multi(X, [wait, wait, wait, wait], yellow).
+X = green .
+```
+
+## Many answers
+
+How about the array in the middle? We can make that a variable too.
+
+```prolog
+?- transition_multi(green, Actions, red).
+Actions = [wait, wait] <cursor>
+```
+
+Prolog tells us that `[wait, wait]` will work, then - interestingly - waits for input. We can hit `Enter` to get back to the original prompt, or hit semicolon `;` to **keep it going**.
+
+```prolog
+?- transition_multi(green, Actions, red).
+Actions = [wait, wait] ;
+Actions = [wait, wait, wait, wait, wait] <cursor>
+```
+
+It appears that not only do two `wait`s bring us from green to red, but so do five. This is because waiting three times brings us back to green (then two more for red). We can keep going with another press of `;`.
+
+```
+?- transition_multi(green, Actions, red).
+Actions = [wait, wait] ;
+Actions = [wait, wait, wait, wait, wait] ;
+Actions = [wait, wait, wait, wait, wait, wait, wait, wait] ;
+Actions = [wait, wait, wait, wait, wait, wait, wait, wait, wait|...] .
+```
+
+We'll be here forever (and ever) so we can just hit `.` to stop.
+
+## What are you waiting for?
+
+Up until now we've been dealing with a single action `wait`, which moves the traffic light from one color to another. Let's introduce two more.
+
+```
+% fsm.prolog
+transition(green, wait, yellow).
+transition(yellow, wait, red).
+transition(red, wait, green).
+
+transition(green, power_outage, blinking).
+transition(yellow, power_outage, blinking).
+transition(red, power_outage, blinking).
+
+transition(blinking, power_on, red).
+```
+
+And query with them like so:
+
+```
+?- [fsm].
+?- transition_multi(green, [power_outage], X).
+X = blinking.
+?- transition_multi(green, [wait, power_outage], X).
+X = blinking.
+```
+
+Now we can answer more interesting questions such as **What two events can occur to take us from green to red?**.
+
+```
+?- transition_multi(green, [A, B], red).
+A = B, B = wait <cursor>
+```
+
+We can wait twice, but Prolog asks if we want to keep going. We do, using `;`.
+
+```
+?- transition_multi(green, [A, B], red).
+A = B, B = wait ;
+A = power_outage,
+B = power_on.
+```
+
+So, we can go from `green` to `red` with two waits, or a `power_outage` and `power_on`. What if we give ourselves four actions?
+
+```
+?- transition_multi(green, [A, B, C, D], red).
+A = B, B = wait,
+C = power_outage,
+D = power_on ;
+A = C, C = power_outage,
+B = D, D = power_on.
+```
+
+We're left with two options:
+* [wait, wait, power_outage, power_on]
+* [power_outage, power_on, power_outage, power_on]
+
+## Closing notes
+
+Writing our program as a series of *relations* instead of instructions unlocks the ability to flip our code upside down. Not only can we feed it input and get output, but we can feed it "output" to determine what "input" we require to produce facts.
+
+When we combine this technique with more interesting data structures like lists, we can *generate* infinite sequences of inputs to get a desired output.
+
+I hope you found this post enlightening, and encourage to fire up [SWI-Prolog](https://www.swi-prolog.org/) to see what other sorts of problems logic programming can help solve.
+
+Until then, see you around [on twitter](https://twitter.com/jdan).
+<!--stackedit_data:
+eyJoaXN0b3J5IjpbLTExMjU4MTIxMDUsMjEwNDk1MDI5NCwtOT
+M3MzE1MjQxLC03MTExMzIyODUsLTEwMzQ5OTEwMzAsMTM2NTMy
+NDk3LC0xNTg2OTcwNjg3LDE4Mjg3MTAzNjQsLTk3MzA1NjU3NF
+19
+-->
diff --git a/index.js b/index.js
new file mode 100644
index 0000000..1265c75
--- /dev/null
+++ b/index.js
@@ -0,0 +1,42 @@
+const glob = require("glob");
+const generateTags = require("./generate-tags.js");
+const loadArticle = require("./load-article.js");
+const saveArticle = require("./save-article.js");
+const saveIndex = require("./save-index.js");
+const saveStaticFile = require("./save-static-file.js");
+const save32x32 = require("./save-32x32.js");
+
+glob("articles/*.md", (err, articles) => {
+ if (err) { throw err; }
+
+ Promise.all(articles.map(loadArticle))
+ .then(articles => {
+ const promises = articles.map(saveArticle);
+
+ promises.push(saveIndex(articles, "output/index.html"));
+ promises.push(generateTags(articles));
+
+ return Promise.all(promises);
+ })
+ .then(
+ () => console.log("Articles built."),
+ err => {
+ console.log("Error building articles.");
+ console.log(err);
+ }
+ );
+});
+
+glob("public/**/*.*", (err, staticFiles) => {
+ if (err) { throw err; }
+
+ Promise.all(staticFiles.map(saveStaticFile))
+ //.then(save32x32)
+ .then(
+ () => console.log("Static files built."),
+ err => {
+ console.log("Error saving static files.");
+ console.log(err);
+ }
+ );
+});
diff --git a/load-article.js b/load-article.js
new file mode 100644
index 0000000..c066a4a
--- /dev/null
+++ b/load-article.js
@@ -0,0 +1,75 @@
+const fm = require("front-matter");
+const fs = require("fs");
+const hljs = require("highlight.js");
+const katex = require("katex");
+const makeTweetUrl = require("./make-tweet-url.js");
+const marked = require("marked");
+const strftime = require("strftime");
+
+marked.setOptions({
+ gfm: true,
+ highlight: (code, lang) => {
+ return lang
+ ? hljs.highlight(lang, code).value
+ : hljs.highlightAuto(code).value;
+ },
+});
+
+function loadArticle(filename) {
+ return new Promise((resolve, reject) => {
+ fs.readFile(filename, (err, data) => {
+ if (err) { return reject(err); }
+
+ const article = fm(data.toString());
+ const body = marked(
+ article.body.replace(/\$\$([^$]*)\$\$/g, (_, tex) =>
+ katex.renderToString(tex, { displayMode: true })
+ )
+ );
+
+ let summary = article.attributes.description;
+ if (!summary) {
+ const paragraphBreak = article.body.match(/\r?\n\r?\n/);
+ if (paragraphBreak) {
+ summary = marked(article.body.slice(0, paragraphBreak.index));
+ } else {
+ summary = article.body;
+ }
+ }
+
+ const rawDate = article.attributes.date;
+ let date = null;
+
+ if (rawDate) {
+ ;["FullYear", "Month", "Date", "Hours"].forEach(field => {
+ rawDate["set" + field](rawDate["getUTC" + field]());
+ });
+
+ // date = strftime("%B %d, %Y", rawDate);
+ date = strftime("%Y. %B. %d.", rawDate);
+ }
+
+ const tweetUrl = makeTweetUrl(article);
+ const defaults = { hidden: false, timeless: false };
+
+ const tags = article.attributes.tags
+ ? article.attributes.tags.split(", ")
+ : [];
+
+ resolve(
+ Object.assign({}, defaults, article.attributes, {
+ filename,
+ summary,
+ date,
+ rawDate,
+ tweetUrl,
+ tags,
+ rawBody: article.body,
+ body,
+ })
+ );
+ });
+ });
+}
+
+module.exports = loadArticle;
diff --git a/make-tweet-url.js b/make-tweet-url.js
new file mode 100644
index 0000000..b440dcf
--- /dev/null
+++ b/make-tweet-url.js
@@ -0,0 +1,11 @@
+function makeTweetUrl(article) {
+ const title = article.attributes.title;
+ const route = article.attributes.route;
+
+ const baseUrl = "https://twitter.com/intent/tweet";
+ const text = "“" + title + "” by @xFuwn – http://blog.fuwn.me" + route;
+
+ return baseUrl + "?text=" + encodeURIComponent(text);
+}
+
+module.exports = makeTweetUrl;
diff --git a/netlify.toml b/netlify.toml
new file mode 100644
index 0000000..a689fe2
--- /dev/null
+++ b/netlify.toml
@@ -0,0 +1,7 @@
+# [[redirects]]
+# from = "https://blog.fuwn.me/*"
+# to = "https://jot-ten.vercel.app/:splat"
+# status = 200
+# force = true
+# [redirects.headers]
+# X-From = "Netlify"
diff --git a/package.json b/package.json
new file mode 100644
index 0000000..c55453c
--- /dev/null
+++ b/package.json
@@ -0,0 +1,46 @@
+{
+ "dependencies": {
+ "ejs": "^2.4.1",
+ "front-matter": "^3.0.2",
+ "glob": "^7.1.4",
+ "highlight.js": "^9.3.0",
+ "katex": "^0.10.2",
+ "marked": "^0.6.2",
+ "mkdirp": "^0.5.1",
+ "ncp": "^2.0.0",
+ "strftime": "^0.10.0"
+ },
+ "devDependencies": {
+ "express": "^4.16.2",
+ "multer": "^1.3.0",
+ "nodemon": "^1.19.0",
+ "serve": "^11.0.0"
+ },
+ "scripts": {
+ "build": "node index.js",
+ "build-prod": "node index.js",
+ "watch": "nodemon index.js",
+ "serve": "cd output && serve -l 8080",
+ "editor": "nodemon editor/editor.js"
+ },
+ "nodemonConfig": {
+ "ignore": [
+ "output/*"
+ ],
+ "ext": "js,md,css,ejs"
+ },
+ "name": "thatjdanisso.cool",
+ "description": "This is a collection of node scripts to build my blog - https://thatjdanisso.cool",
+ "version": "1.0.0",
+ "main": "index.js",
+ "repository": {
+ "type": "git",
+ "url": "git+https://github.com/jdan/thatjdanisso.cool.git"
+ },
+ "author": "Jordan Scales <[email protected]>",
+ "license": "ISC",
+ "bugs": {
+ "url": "https://github.com/jdan/thatjdanisso.cool/issues"
+ },
+ "homepage": "https://github.com/jdan/thatjdanisso.cool#readme"
+}
diff --git a/plugins/tweet.js b/plugins/tweet.js
new file mode 100644
index 0000000..76b454b
--- /dev/null
+++ b/plugins/tweet.js
@@ -0,0 +1,6 @@
+module.exports = function (title, route) {
+ const baseUrl = "https://twitter.com/intent/tweet";
+ const text = "“" + title + "” by @jdan – http://thatjdanisso.cool" + route;
+
+ return baseUrl + "?text=" + encodeURIComponent(text);
+};
diff --git a/public/css/bowling.css b/public/css/bowling.css
new file mode 100644
index 0000000..332bc93
--- /dev/null
+++ b/public/css/bowling.css
@@ -0,0 +1,178 @@
+button {
+ color: #9d261d;
+ background: none;
+ font-family: inherit;
+ font-size: inherit;
+ border: 3px solid #9d261d;
+ padding: 15px;
+ margin-bottom: 10px;
+}
+
+button:hover {
+ color: #fff;
+ background-color: #9d261d;
+ cursor: pointer;
+}
+
+#demo {
+ margin: 40px auto;
+ position: relative;
+}
+
+#stats {
+ position: absolute;
+ top: 0;
+ right: 0;
+}
+
+.stats-label {
+ width: 80px;
+}
+
+#scoreboard {
+ height: 76px;
+ display: flex;
+ margin-bottom: 40px;
+}
+
+.scoreboard-half {
+ display: flex;
+ flex-grow: 15;
+}
+
+.scoreboard-half:first-of-type .frame:last-child {
+ border-right: none;
+}
+
+.scoreboard-half:nth-of-type(2) {
+ flex-grow: 16;
+}
+
+.frame {
+ position: relative;
+ box-sizing: border-box;
+ display: flex;
+ justify-content: flex-end;
+ flex-grow: 2;
+ height: 76px;
+ border: 3px solid #222;
+ border-right: none;
+}
+
+.frame:last-child {
+ flex-grow: 3;
+ border-right: 3px solid #222;
+}
+
+.square {
+ border-bottom: 3px solid #222;
+ border-left: 3px solid #222;
+ width: 23px;
+ height: 23px;
+ line-height: 23px;
+
+ text-align: center;
+}
+
+.square:first-of-type {
+ border: none;
+}
+
+.score {
+ position: absolute;
+ bottom: 5px;
+ left: 5px;
+}
+
+#graph {
+ display: flex;
+ height: 300px;
+ align-items: flex-end;
+}
+
+.bar {
+ background-color: #9d261d;
+ flex-grow: 1;
+ margin-right: 1px;
+}
+
+#controls {
+ display: flex;
+}
+
+#throw,
+#game {
+ flex-grow: 3;
+ margin-right: 10px;
+}
+
+#many-games {
+ flex-grow: 4;
+}
+
+.mobile-spacer {
+ display: none;
+}
+
+@media (max-width: 650px) {
+ #demo {
+ margin: 20px auto;
+ }
+
+ button:after {
+ clear: both;
+ }
+
+ #stats {
+ font-size: 16px;
+ }
+
+ .bar {
+ margin: 0;
+ }
+
+ #scoreboard {
+ flex-wrap: wrap;
+ height: auto;
+ }
+
+ #graph {
+ height: 200px;
+ }
+
+ .scoreboard-half:first-of-type .frame:nth-of-type(5) {
+ border-right: 3px solid #222;
+ }
+
+ .frame {
+ flex-basis: 0;
+ }
+
+ .scoreboard-half:first-of-type .frame {
+ border-bottom: none;
+ flex-grow: 2;
+ }
+
+ .mobile-spacer {
+ display: block;
+ flex-grow: 1;
+ }
+
+ #controls {
+ flex-wrap: wrap;
+ }
+
+ #controls button {
+ margin: 0 0 10px;
+ }
+
+ button#throw {
+ margin-right: 10px;
+ }
+}
+
+@media (max-width: 425px) {
+ button#throw {
+ margin-right: 0;
+ }
+}
diff --git a/public/css/color-blindness.css b/public/css/color-blindness.css
new file mode 100644
index 0000000..ebe492b
--- /dev/null
+++ b/public/css/color-blindness.css
@@ -0,0 +1,30 @@
+body {
+ filter: url("#color-filter");
+ -webkit-filter: url("#color-filter");
+}
+
+#controls {
+ margin-bottom: 60px;
+}
+
+img {
+ border: none;
+ width: 50%;
+ float: left;
+}
+
+svg {
+ width: 0;
+ height: 0;
+}
+
+.control .label-text {
+ display: inline-block;
+ width: 40px;
+}
+
+.control .label-value {
+ display: inline-block;
+ text-align: right;
+ width: 70px;
+}
diff --git a/public/css/demos/clicking.css b/public/css/demos/clicking.css
new file mode 100644
index 0000000..9a9016e
--- /dev/null
+++ b/public/css/demos/clicking.css
@@ -0,0 +1,46 @@
+.a11y-onclick .button {
+ text-align: center;
+ margin-right: 20px;
+ width: 100px;
+ color: white;
+ padding: 15px 25px;
+ background-color: #0074f6;
+}
+
+.a11y-onclick.active:not(.no-active) .button {
+ background-color: #004a93;
+}
+
+.a11y-onclick .output {
+ width: 200px;
+ padding: 15px;
+ background-color: #e9e9e9;
+}
+
+.a11y-onclick.active .mouse-pointer {
+ top: 29px;
+}
+
+.a11y-onclick .row {
+ width: 100%;
+}
+
+.a11y-onclick .enter-key {
+ margin: 0 auto 20px;
+ width: 129px;
+ padding: 15px 25px;
+ background-color: #222222;
+ color: #ffffff;
+ text-align: right;
+ border-radius: 7px;
+ border-bottom: 5px solid #666666;
+}
+
+.a11y-onclick.active .enter-key {
+ margin-top: 3px;
+ border-bottom-width: 2px;
+}
+
+.a11y-onclick .button.focused {
+ outline: 3px solid #000000;
+}
diff --git a/public/css/demos/hover.css b/public/css/demos/hover.css
new file mode 100644
index 0000000..23592da
--- /dev/null
+++ b/public/css/demos/hover.css
@@ -0,0 +1,120 @@
+.hover-a11y .row {
+ background-color: #004a93;
+}
+
+.hover-a11y .button {
+ color: #ffffff;
+ padding: 15px 25px;
+ text-align: center;
+}
+
+.hover-a11y .mouse-pointer {
+ animation: mousepos 6s ease-in-out 0s infinite;
+ -webkit-animation: mousepos 6s ease-in-out 0s infinite;
+ -moz-animation: mousepos 6s ease-in-out 0s infinite;
+ -o-animation: mousepos 6s ease-in-out 0s infinite;
+}
+
+.hover-a11y:not(.no-active) .button.active {
+ color: #aabbdd;
+}
+
+.hover-a11y.with-underline:not(.no-active) .button.active {
+ text-decoration: underline;
+}
+
+@-webkit-keyframes mousepos {
+ 0% {
+ -webkit-transform: translateX(30px) rotateZ(-20deg);
+ }
+ 15% {
+ -webkit-transform: translateX(100px) rotateZ(-20deg);
+ }
+ 20% {
+ -webkit-transform: translateX(100px) rotateZ(-20deg);
+ }
+ 40% {
+ -webkit-transform: translateX(300px) rotateZ(-20deg) translateY(10px);
+ }
+ 45% {
+ -webkit-transform: translateX(300px) rotateZ(-20deg) translateY(10px);
+ }
+ 80% {
+ -webkit-transform: translateX(30px) rotateZ(-20deg);
+ }
+ 100% {
+ -webkit-transform: translateX(30px) rotateZ(-20deg);
+ }
+}
+
+@-moz-keyframes mousepos {
+ 0% {
+ -moz-transform: translateX(30px) rotateZ(-20deg);
+ }
+ 15% {
+ -moz-transform: translateX(100px) rotateZ(-20deg);
+ }
+ 20% {
+ -moz-transform: translateX(100px) rotateZ(-20deg);
+ }
+ 40% {
+ -moz-transform: translateX(300px) rotateZ(-20deg) translateY(10px);
+ }
+ 45% {
+ -moz-transform: translateX(300px) rotateZ(-20deg) translateY(10px);
+ }
+ 80% {
+ -moz-transform: translateX(30px) rotateZ(-20deg);
+ }
+ 100% {
+ -moz-transform: translateX(30px) rotateZ(-20deg);
+ }
+}
+
+@-o-keyframes mousepos {
+ 0% {
+ -o-transform: translateX(30px) rotateZ(-20deg);
+ }
+ 15% {
+ -o-transform: translateX(100px) rotateZ(-20deg);
+ }
+ 20% {
+ -o-transform: translateX(100px) rotateZ(-20deg);
+ }
+ 40% {
+ -o-transform: translateX(300px) rotateZ(-20deg) translateY(10px);
+ }
+ 45% {
+ -o-transform: translateX(300px) rotateZ(-20deg) translateY(10px);
+ }
+ 80% {
+ -o-transform: translateX(30px) rotateZ(-20deg);
+ }
+ 100% {
+ -o-transform: translateX(30px) rotateZ(-20deg);
+ }
+}
+
+@keyframes mousepos {
+ 0% {
+ transform: translateX(30px) rotateZ(-20deg);
+ }
+ 15% {
+ transform: translateX(100px) rotateZ(-20deg);
+ }
+ 20% {
+ transform: translateX(100px) rotateZ(-20deg);
+ }
+ 40% {
+ transform: translateX(300px) rotateZ(-20deg) translateY(10px);
+ }
+ 45% {
+ transform: translateX(300px) rotateZ(-20deg) translateY(10px);
+ }
+ 80% {
+ transform: translateX(30px) rotateZ(-20deg);
+ }
+ 100% {
+ transform: translateX(30px) rotateZ(-20deg);
+ }
+}
diff --git a/public/css/demos/shared.css b/public/css/demos/shared.css
new file mode 100644
index 0000000..7794d32
--- /dev/null
+++ b/public/css/demos/shared.css
@@ -0,0 +1,28 @@
+.mouse-pointer {
+ -webkit-transform: rotateZ(-20deg);
+ -moz-transform: rotateZ(-20deg);
+ -ms-transform: rotateZ(-20deg);
+ -o-transform: rotateZ(-20deg);
+ transform: rotateZ(-20deg);
+ position: relative;
+ width: 0;
+ top: 27px;
+ right: 45px;
+}
+
+.mouse-pointer .head {
+ border-left: 7px solid transparent;
+ border-right: 6px solid transparent;
+ border-bottom: 16px solid white;
+ width: 0;
+ height: 0;
+}
+
+.mouse-pointer .tail {
+ width: 3px;
+ height: 8px;
+ position: relative;
+ left: 5px;
+ top: -1px;
+ background-color: #ffffff;
+}
diff --git a/public/css/fonts/KaTeX_AMS-Regular.ttf b/public/css/fonts/KaTeX_AMS-Regular.ttf
new file mode 100644
index 0000000..afcd2eb
--- /dev/null
+++ b/public/css/fonts/KaTeX_AMS-Regular.ttf
Binary files differ
diff --git a/public/css/fonts/KaTeX_AMS-Regular.woff b/public/css/fonts/KaTeX_AMS-Regular.woff
new file mode 100644
index 0000000..4f57515
--- /dev/null
+++ b/public/css/fonts/KaTeX_AMS-Regular.woff
Binary files differ
diff --git a/public/css/fonts/KaTeX_AMS-Regular.woff2 b/public/css/fonts/KaTeX_AMS-Regular.woff2
new file mode 100644
index 0000000..b982d6e
--- /dev/null
+++ b/public/css/fonts/KaTeX_AMS-Regular.woff2
Binary files differ
diff --git a/public/css/fonts/KaTeX_Caligraphic-Bold.ttf b/public/css/fonts/KaTeX_Caligraphic-Bold.ttf
new file mode 100644
index 0000000..f84148d
--- /dev/null
+++ b/public/css/fonts/KaTeX_Caligraphic-Bold.ttf
Binary files differ
diff --git a/public/css/fonts/KaTeX_Caligraphic-Bold.woff b/public/css/fonts/KaTeX_Caligraphic-Bold.woff
new file mode 100644
index 0000000..ab56ab7
--- /dev/null
+++ b/public/css/fonts/KaTeX_Caligraphic-Bold.woff
Binary files differ
diff --git a/public/css/fonts/KaTeX_Caligraphic-Bold.woff2 b/public/css/fonts/KaTeX_Caligraphic-Bold.woff2
new file mode 100644
index 0000000..710c261
--- /dev/null
+++ b/public/css/fonts/KaTeX_Caligraphic-Bold.woff2
Binary files differ
diff --git a/public/css/fonts/KaTeX_Caligraphic-Regular.ttf b/public/css/fonts/KaTeX_Caligraphic-Regular.ttf
new file mode 100644
index 0000000..97814db
--- /dev/null
+++ b/public/css/fonts/KaTeX_Caligraphic-Regular.ttf
Binary files differ
diff --git a/public/css/fonts/KaTeX_Caligraphic-Regular.woff b/public/css/fonts/KaTeX_Caligraphic-Regular.woff
new file mode 100644
index 0000000..aec8a33
--- /dev/null
+++ b/public/css/fonts/KaTeX_Caligraphic-Regular.woff
Binary files differ
diff --git a/public/css/fonts/KaTeX_Caligraphic-Regular.woff2 b/public/css/fonts/KaTeX_Caligraphic-Regular.woff2
new file mode 100644
index 0000000..ee5193d
--- /dev/null
+++ b/public/css/fonts/KaTeX_Caligraphic-Regular.woff2
Binary files differ
diff --git a/public/css/fonts/KaTeX_Fraktur-Bold.ttf b/public/css/fonts/KaTeX_Fraktur-Bold.ttf
new file mode 100644
index 0000000..483a7cd
--- /dev/null
+++ b/public/css/fonts/KaTeX_Fraktur-Bold.ttf
Binary files differ
diff --git a/public/css/fonts/KaTeX_Fraktur-Bold.woff b/public/css/fonts/KaTeX_Fraktur-Bold.woff
new file mode 100644
index 0000000..189fea5
--- /dev/null
+++ b/public/css/fonts/KaTeX_Fraktur-Bold.woff
Binary files differ
diff --git a/public/css/fonts/KaTeX_Fraktur-Bold.woff2 b/public/css/fonts/KaTeX_Fraktur-Bold.woff2
new file mode 100644
index 0000000..dc3bd4c
--- /dev/null
+++ b/public/css/fonts/KaTeX_Fraktur-Bold.woff2
Binary files differ
diff --git a/public/css/fonts/KaTeX_Fraktur-Regular.ttf b/public/css/fonts/KaTeX_Fraktur-Regular.ttf
new file mode 100644
index 0000000..9aa5f67
--- /dev/null
+++ b/public/css/fonts/KaTeX_Fraktur-Regular.ttf
Binary files differ
diff --git a/public/css/fonts/KaTeX_Fraktur-Regular.woff b/public/css/fonts/KaTeX_Fraktur-Regular.woff
new file mode 100644
index 0000000..d01450e
--- /dev/null
+++ b/public/css/fonts/KaTeX_Fraktur-Regular.woff
Binary files differ
diff --git a/public/css/fonts/KaTeX_Fraktur-Regular.woff2 b/public/css/fonts/KaTeX_Fraktur-Regular.woff2
new file mode 100644
index 0000000..7eeba37
--- /dev/null
+++ b/public/css/fonts/KaTeX_Fraktur-Regular.woff2
Binary files differ
diff --git a/public/css/fonts/KaTeX_Main-Bold.ttf b/public/css/fonts/KaTeX_Main-Bold.ttf
new file mode 100644
index 0000000..dc0185a
--- /dev/null
+++ b/public/css/fonts/KaTeX_Main-Bold.ttf
Binary files differ
diff --git a/public/css/fonts/KaTeX_Main-Bold.woff b/public/css/fonts/KaTeX_Main-Bold.woff
new file mode 100644
index 0000000..acf48e6
--- /dev/null
+++ b/public/css/fonts/KaTeX_Main-Bold.woff
Binary files differ
diff --git a/public/css/fonts/KaTeX_Main-Bold.woff2 b/public/css/fonts/KaTeX_Main-Bold.woff2
new file mode 100644
index 0000000..cf5abab
--- /dev/null
+++ b/public/css/fonts/KaTeX_Main-Bold.woff2
Binary files differ
diff --git a/public/css/fonts/KaTeX_Main-BoldItalic.ttf b/public/css/fonts/KaTeX_Main-BoldItalic.ttf
new file mode 100644
index 0000000..4346f17
--- /dev/null
+++ b/public/css/fonts/KaTeX_Main-BoldItalic.ttf
Binary files differ
diff --git a/public/css/fonts/KaTeX_Main-BoldItalic.woff b/public/css/fonts/KaTeX_Main-BoldItalic.woff
new file mode 100644
index 0000000..d2cfe4e
--- /dev/null
+++ b/public/css/fonts/KaTeX_Main-BoldItalic.woff
Binary files differ
diff --git a/public/css/fonts/KaTeX_Main-BoldItalic.woff2 b/public/css/fonts/KaTeX_Main-BoldItalic.woff2
new file mode 100644
index 0000000..d0178f4
--- /dev/null
+++ b/public/css/fonts/KaTeX_Main-BoldItalic.woff2
Binary files differ
diff --git a/public/css/fonts/KaTeX_Main-Italic.ttf b/public/css/fonts/KaTeX_Main-Italic.ttf
new file mode 100644
index 0000000..f2c3eba
--- /dev/null
+++ b/public/css/fonts/KaTeX_Main-Italic.ttf
Binary files differ
diff --git a/public/css/fonts/KaTeX_Main-Italic.woff b/public/css/fonts/KaTeX_Main-Italic.woff
new file mode 100644
index 0000000..1184295
--- /dev/null
+++ b/public/css/fonts/KaTeX_Main-Italic.woff
Binary files differ
diff --git a/public/css/fonts/KaTeX_Main-Italic.woff2 b/public/css/fonts/KaTeX_Main-Italic.woff2
new file mode 100644
index 0000000..aa05e14
--- /dev/null
+++ b/public/css/fonts/KaTeX_Main-Italic.woff2
Binary files differ
diff --git a/public/css/fonts/KaTeX_Main-Regular.ttf b/public/css/fonts/KaTeX_Main-Regular.ttf
new file mode 100644
index 0000000..8acb365
--- /dev/null
+++ b/public/css/fonts/KaTeX_Main-Regular.ttf
Binary files differ
diff --git a/public/css/fonts/KaTeX_Main-Regular.woff b/public/css/fonts/KaTeX_Main-Regular.woff
new file mode 100644
index 0000000..9f8228f
--- /dev/null
+++ b/public/css/fonts/KaTeX_Main-Regular.woff
Binary files differ
diff --git a/public/css/fonts/KaTeX_Main-Regular.woff2 b/public/css/fonts/KaTeX_Main-Regular.woff2
new file mode 100644
index 0000000..e3f71eb
--- /dev/null
+++ b/public/css/fonts/KaTeX_Main-Regular.woff2
Binary files differ
diff --git a/public/css/fonts/KaTeX_Math-BoldItalic.ttf b/public/css/fonts/KaTeX_Math-BoldItalic.ttf
new file mode 100644
index 0000000..a645df6
--- /dev/null
+++ b/public/css/fonts/KaTeX_Math-BoldItalic.ttf
Binary files differ
diff --git a/public/css/fonts/KaTeX_Math-BoldItalic.woff b/public/css/fonts/KaTeX_Math-BoldItalic.woff
new file mode 100644
index 0000000..87d4f22
--- /dev/null
+++ b/public/css/fonts/KaTeX_Math-BoldItalic.woff
Binary files differ
diff --git a/public/css/fonts/KaTeX_Math-BoldItalic.woff2 b/public/css/fonts/KaTeX_Math-BoldItalic.woff2
new file mode 100644
index 0000000..83b4996
--- /dev/null
+++ b/public/css/fonts/KaTeX_Math-BoldItalic.woff2
Binary files differ
diff --git a/public/css/fonts/KaTeX_Math-Italic.ttf b/public/css/fonts/KaTeX_Math-Italic.ttf
new file mode 100644
index 0000000..9c38359
--- /dev/null
+++ b/public/css/fonts/KaTeX_Math-Italic.ttf
Binary files differ
diff --git a/public/css/fonts/KaTeX_Math-Italic.woff b/public/css/fonts/KaTeX_Math-Italic.woff
new file mode 100644
index 0000000..959746e
--- /dev/null
+++ b/public/css/fonts/KaTeX_Math-Italic.woff
Binary files differ
diff --git a/public/css/fonts/KaTeX_Math-Italic.woff2 b/public/css/fonts/KaTeX_Math-Italic.woff2
new file mode 100644
index 0000000..e3ea522
--- /dev/null
+++ b/public/css/fonts/KaTeX_Math-Italic.woff2
Binary files differ
diff --git a/public/css/fonts/KaTeX_SansSerif-Bold.ttf b/public/css/fonts/KaTeX_SansSerif-Bold.ttf
new file mode 100644
index 0000000..ff10851
--- /dev/null
+++ b/public/css/fonts/KaTeX_SansSerif-Bold.ttf
Binary files differ
diff --git a/public/css/fonts/KaTeX_SansSerif-Bold.woff b/public/css/fonts/KaTeX_SansSerif-Bold.woff
new file mode 100644
index 0000000..f0d6ea7
--- /dev/null
+++ b/public/css/fonts/KaTeX_SansSerif-Bold.woff
Binary files differ
diff --git a/public/css/fonts/KaTeX_SansSerif-Bold.woff2 b/public/css/fonts/KaTeX_SansSerif-Bold.woff2
new file mode 100644
index 0000000..4cf8f14
--- /dev/null
+++ b/public/css/fonts/KaTeX_SansSerif-Bold.woff2
Binary files differ
diff --git a/public/css/fonts/KaTeX_SansSerif-Italic.ttf b/public/css/fonts/KaTeX_SansSerif-Italic.ttf
new file mode 100644
index 0000000..3dd7671
--- /dev/null
+++ b/public/css/fonts/KaTeX_SansSerif-Italic.ttf
Binary files differ
diff --git a/public/css/fonts/KaTeX_SansSerif-Italic.woff b/public/css/fonts/KaTeX_SansSerif-Italic.woff
new file mode 100644
index 0000000..9da0dfe
--- /dev/null
+++ b/public/css/fonts/KaTeX_SansSerif-Italic.woff
Binary files differ
diff --git a/public/css/fonts/KaTeX_SansSerif-Italic.woff2 b/public/css/fonts/KaTeX_SansSerif-Italic.woff2
new file mode 100644
index 0000000..ce19ae0
--- /dev/null
+++ b/public/css/fonts/KaTeX_SansSerif-Italic.woff2
Binary files differ
diff --git a/public/css/fonts/KaTeX_SansSerif-Regular.ttf b/public/css/fonts/KaTeX_SansSerif-Regular.ttf
new file mode 100644
index 0000000..f117cd6
--- /dev/null
+++ b/public/css/fonts/KaTeX_SansSerif-Regular.ttf
Binary files differ
diff --git a/public/css/fonts/KaTeX_SansSerif-Regular.woff b/public/css/fonts/KaTeX_SansSerif-Regular.woff
new file mode 100644
index 0000000..6ed9878
--- /dev/null
+++ b/public/css/fonts/KaTeX_SansSerif-Regular.woff
Binary files differ
diff --git a/public/css/fonts/KaTeX_SansSerif-Regular.woff2 b/public/css/fonts/KaTeX_SansSerif-Regular.woff2
new file mode 100644
index 0000000..2761149
--- /dev/null
+++ b/public/css/fonts/KaTeX_SansSerif-Regular.woff2
Binary files differ
diff --git a/public/css/fonts/KaTeX_Script-Regular.ttf b/public/css/fonts/KaTeX_Script-Regular.ttf
new file mode 100644
index 0000000..e6f3454
--- /dev/null
+++ b/public/css/fonts/KaTeX_Script-Regular.ttf
Binary files differ
diff --git a/public/css/fonts/KaTeX_Script-Regular.woff b/public/css/fonts/KaTeX_Script-Regular.woff
new file mode 100644
index 0000000..4a48e65
--- /dev/null
+++ b/public/css/fonts/KaTeX_Script-Regular.woff
Binary files differ
diff --git a/public/css/fonts/KaTeX_Script-Regular.woff2 b/public/css/fonts/KaTeX_Script-Regular.woff2
new file mode 100644
index 0000000..b0aed19
--- /dev/null
+++ b/public/css/fonts/KaTeX_Script-Regular.woff2
Binary files differ
diff --git a/public/css/fonts/KaTeX_Size1-Regular.ttf b/public/css/fonts/KaTeX_Size1-Regular.ttf
new file mode 100644
index 0000000..37faa0f
--- /dev/null
+++ b/public/css/fonts/KaTeX_Size1-Regular.ttf
Binary files differ
diff --git a/public/css/fonts/KaTeX_Size1-Regular.woff b/public/css/fonts/KaTeX_Size1-Regular.woff
new file mode 100644
index 0000000..0832f7a
--- /dev/null
+++ b/public/css/fonts/KaTeX_Size1-Regular.woff
Binary files differ
diff --git a/public/css/fonts/KaTeX_Size1-Regular.woff2 b/public/css/fonts/KaTeX_Size1-Regular.woff2
new file mode 100644
index 0000000..483e7b6
--- /dev/null
+++ b/public/css/fonts/KaTeX_Size1-Regular.woff2
Binary files differ
diff --git a/public/css/fonts/KaTeX_Size2-Regular.ttf b/public/css/fonts/KaTeX_Size2-Regular.ttf
new file mode 100644
index 0000000..cf32623
--- /dev/null
+++ b/public/css/fonts/KaTeX_Size2-Regular.ttf
Binary files differ
diff --git a/public/css/fonts/KaTeX_Size2-Regular.woff b/public/css/fonts/KaTeX_Size2-Regular.woff
new file mode 100644
index 0000000..14f6485
--- /dev/null
+++ b/public/css/fonts/KaTeX_Size2-Regular.woff
Binary files differ
diff --git a/public/css/fonts/KaTeX_Size2-Regular.woff2 b/public/css/fonts/KaTeX_Size2-Regular.woff2
new file mode 100644
index 0000000..5ff7060
--- /dev/null
+++ b/public/css/fonts/KaTeX_Size2-Regular.woff2
Binary files differ
diff --git a/public/css/fonts/KaTeX_Size3-Regular.ttf b/public/css/fonts/KaTeX_Size3-Regular.ttf
new file mode 100644
index 0000000..ff7e2b9
--- /dev/null
+++ b/public/css/fonts/KaTeX_Size3-Regular.ttf
Binary files differ
diff --git a/public/css/fonts/KaTeX_Size3-Regular.woff b/public/css/fonts/KaTeX_Size3-Regular.woff
new file mode 100644
index 0000000..d3626ce
--- /dev/null
+++ b/public/css/fonts/KaTeX_Size3-Regular.woff
Binary files differ
diff --git a/public/css/fonts/KaTeX_Size3-Regular.woff2 b/public/css/fonts/KaTeX_Size3-Regular.woff2
new file mode 100644
index 0000000..e45ca49
--- /dev/null
+++ b/public/css/fonts/KaTeX_Size3-Regular.woff2
Binary files differ
diff --git a/public/css/fonts/KaTeX_Size4-Regular.ttf b/public/css/fonts/KaTeX_Size4-Regular.ttf
new file mode 100644
index 0000000..3034091
--- /dev/null
+++ b/public/css/fonts/KaTeX_Size4-Regular.ttf
Binary files differ
diff --git a/public/css/fonts/KaTeX_Size4-Regular.woff b/public/css/fonts/KaTeX_Size4-Regular.woff
new file mode 100644
index 0000000..93c57a6
--- /dev/null
+++ b/public/css/fonts/KaTeX_Size4-Regular.woff
Binary files differ
diff --git a/public/css/fonts/KaTeX_Size4-Regular.woff2 b/public/css/fonts/KaTeX_Size4-Regular.woff2
new file mode 100644
index 0000000..53b65af
--- /dev/null
+++ b/public/css/fonts/KaTeX_Size4-Regular.woff2
Binary files differ
diff --git a/public/css/fonts/KaTeX_Typewriter-Regular.ttf b/public/css/fonts/KaTeX_Typewriter-Regular.ttf
new file mode 100644
index 0000000..2fd8529
--- /dev/null
+++ b/public/css/fonts/KaTeX_Typewriter-Regular.ttf
Binary files differ
diff --git a/public/css/fonts/KaTeX_Typewriter-Regular.woff b/public/css/fonts/KaTeX_Typewriter-Regular.woff
new file mode 100644
index 0000000..e90fa2b
--- /dev/null
+++ b/public/css/fonts/KaTeX_Typewriter-Regular.woff
Binary files differ
diff --git a/public/css/fonts/KaTeX_Typewriter-Regular.woff2 b/public/css/fonts/KaTeX_Typewriter-Regular.woff2
new file mode 100644
index 0000000..e40ab15
--- /dev/null
+++ b/public/css/fonts/KaTeX_Typewriter-Regular.woff2
Binary files differ
diff --git a/public/css/katex.min.css b/public/css/katex.min.css
new file mode 100644
index 0000000..7549d6e
--- /dev/null
+++ b/public/css/katex.min.css
@@ -0,0 +1,1052 @@
+@font-face {
+ font-family: KaTeX_AMS;
+ src: url(fonts/KaTeX_AMS-Regular.woff2) format("woff2"),
+ url(fonts/KaTeX_AMS-Regular.woff) format("woff"),
+ url(fonts/KaTeX_AMS-Regular.ttf) format("truetype");
+ font-weight: 400;
+ font-style: normal;
+}
+@font-face {
+ font-family: KaTeX_Caligraphic;
+ src: url(fonts/KaTeX_Caligraphic-Bold.woff2) format("woff2"),
+ url(fonts/KaTeX_Caligraphic-Bold.woff) format("woff"),
+ url(fonts/KaTeX_Caligraphic-Bold.ttf) format("truetype");
+ font-weight: 700;
+ font-style: normal;
+}
+@font-face {
+ font-family: KaTeX_Caligraphic;
+ src: url(fonts/KaTeX_Caligraphic-Regular.woff2) format("woff2"),
+ url(fonts/KaTeX_Caligraphic-Regular.woff) format("woff"),
+ url(fonts/KaTeX_Caligraphic-Regular.ttf) format("truetype");
+ font-weight: 400;
+ font-style: normal;
+}
+@font-face {
+ font-family: KaTeX_Fraktur;
+ src: url(fonts/KaTeX_Fraktur-Bold.woff2) format("woff2"),
+ url(fonts/KaTeX_Fraktur-Bold.woff) format("woff"),
+ url(fonts/KaTeX_Fraktur-Bold.ttf) format("truetype");
+ font-weight: 700;
+ font-style: normal;
+}
+@font-face {
+ font-family: KaTeX_Fraktur;
+ src: url(fonts/KaTeX_Fraktur-Regular.woff2) format("woff2"),
+ url(fonts/KaTeX_Fraktur-Regular.woff) format("woff"),
+ url(fonts/KaTeX_Fraktur-Regular.ttf) format("truetype");
+ font-weight: 400;
+ font-style: normal;
+}
+@font-face {
+ font-family: KaTeX_Main;
+ src: url(fonts/KaTeX_Main-Bold.woff2) format("woff2"),
+ url(fonts/KaTeX_Main-Bold.woff) format("woff"),
+ url(fonts/KaTeX_Main-Bold.ttf) format("truetype");
+ font-weight: 700;
+ font-style: normal;
+}
+@font-face {
+ font-family: KaTeX_Main;
+ src: url(fonts/KaTeX_Main-BoldItalic.woff2) format("woff2"),
+ url(fonts/KaTeX_Main-BoldItalic.woff) format("woff"),
+ url(fonts/KaTeX_Main-BoldItalic.ttf) format("truetype");
+ font-weight: 700;
+ font-style: italic;
+}
+@font-face {
+ font-family: KaTeX_Main;
+ src: url(fonts/KaTeX_Main-Italic.woff2) format("woff2"),
+ url(fonts/KaTeX_Main-Italic.woff) format("woff"),
+ url(fonts/KaTeX_Main-Italic.ttf) format("truetype");
+ font-weight: 400;
+ font-style: italic;
+}
+@font-face {
+ font-family: KaTeX_Main;
+ src: url(fonts/KaTeX_Main-Regular.woff2) format("woff2"),
+ url(fonts/KaTeX_Main-Regular.woff) format("woff"),
+ url(fonts/KaTeX_Main-Regular.ttf) format("truetype");
+ font-weight: 400;
+ font-style: normal;
+}
+@font-face {
+ font-family: KaTeX_Math;
+ src: url(fonts/KaTeX_Math-BoldItalic.woff2) format("woff2"),
+ url(fonts/KaTeX_Math-BoldItalic.woff) format("woff"),
+ url(fonts/KaTeX_Math-BoldItalic.ttf) format("truetype");
+ font-weight: 700;
+ font-style: italic;
+}
+@font-face {
+ font-family: KaTeX_Math;
+ src: url(fonts/KaTeX_Math-Italic.woff2) format("woff2"),
+ url(fonts/KaTeX_Math-Italic.woff) format("woff"),
+ url(fonts/KaTeX_Math-Italic.ttf) format("truetype");
+ font-weight: 400;
+ font-style: italic;
+}
+@font-face {
+ font-family: "KaTeX_SansSerif";
+ src: url(fonts/KaTeX_SansSerif-Bold.woff2) format("woff2"),
+ url(fonts/KaTeX_SansSerif-Bold.woff) format("woff"),
+ url(fonts/KaTeX_SansSerif-Bold.ttf) format("truetype");
+ font-weight: 700;
+ font-style: normal;
+}
+@font-face {
+ font-family: "KaTeX_SansSerif";
+ src: url(fonts/KaTeX_SansSerif-Italic.woff2) format("woff2"),
+ url(fonts/KaTeX_SansSerif-Italic.woff) format("woff"),
+ url(fonts/KaTeX_SansSerif-Italic.ttf) format("truetype");
+ font-weight: 400;
+ font-style: italic;
+}
+@font-face {
+ font-family: "KaTeX_SansSerif";
+ src: url(fonts/KaTeX_SansSerif-Regular.woff2) format("woff2"),
+ url(fonts/KaTeX_SansSerif-Regular.woff) format("woff"),
+ url(fonts/KaTeX_SansSerif-Regular.ttf) format("truetype");
+ font-weight: 400;
+ font-style: normal;
+}
+@font-face {
+ font-family: KaTeX_Script;
+ src: url(fonts/KaTeX_Script-Regular.woff2) format("woff2"),
+ url(fonts/KaTeX_Script-Regular.woff) format("woff"),
+ url(fonts/KaTeX_Script-Regular.ttf) format("truetype");
+ font-weight: 400;
+ font-style: normal;
+}
+@font-face {
+ font-family: KaTeX_Size1;
+ src: url(fonts/KaTeX_Size1-Regular.woff2) format("woff2"),
+ url(fonts/KaTeX_Size1-Regular.woff) format("woff"),
+ url(fonts/KaTeX_Size1-Regular.ttf) format("truetype");
+ font-weight: 400;
+ font-style: normal;
+}
+@font-face {
+ font-family: KaTeX_Size2;
+ src: url(fonts/KaTeX_Size2-Regular.woff2) format("woff2"),
+ url(fonts/KaTeX_Size2-Regular.woff) format("woff"),
+ url(fonts/KaTeX_Size2-Regular.ttf) format("truetype");
+ font-weight: 400;
+ font-style: normal;
+}
+@font-face {
+ font-family: KaTeX_Size3;
+ src: url(fonts/KaTeX_Size3-Regular.woff2) format("woff2"),
+ url(fonts/KaTeX_Size3-Regular.woff) format("woff"),
+ url(fonts/KaTeX_Size3-Regular.ttf) format("truetype");
+ font-weight: 400;
+ font-style: normal;
+}
+@font-face {
+ font-family: KaTeX_Size4;
+ src: url(fonts/KaTeX_Size4-Regular.woff2) format("woff2"),
+ url(fonts/KaTeX_Size4-Regular.woff) format("woff"),
+ url(fonts/KaTeX_Size4-Regular.ttf) format("truetype");
+ font-weight: 400;
+ font-style: normal;
+}
+@font-face {
+ font-family: KaTeX_Typewriter;
+ src: url(fonts/KaTeX_Typewriter-Regular.woff2) format("woff2"),
+ url(fonts/KaTeX_Typewriter-Regular.woff) format("woff"),
+ url(fonts/KaTeX_Typewriter-Regular.ttf) format("truetype");
+ font-weight: 400;
+ font-style: normal;
+}
+.katex {
+ font: normal 1.21em KaTeX_Main, Times New Roman, serif;
+ line-height: 1.2;
+ text-indent: 0;
+ text-rendering: auto;
+}
+.katex * {
+ -ms-high-contrast-adjust: none !important;
+}
+.katex .katex-version:after {
+ content: "0.10.2";
+}
+.katex .katex-mathml {
+ position: absolute;
+ clip: rect(1px, 1px, 1px, 1px);
+ padding: 0;
+ border: 0;
+ height: 1px;
+ width: 1px;
+ overflow: hidden;
+}
+.katex .katex-html > .newline {
+ display: block;
+}
+.katex .base {
+ position: relative;
+ white-space: nowrap;
+ width: min-content;
+}
+.katex .base,
+.katex .strut {
+ display: inline-block;
+}
+.katex .textbf {
+ font-weight: 700;
+}
+.katex .textit {
+ font-style: italic;
+}
+.katex .textrm {
+ font-family: KaTeX_Main;
+}
+.katex .textsf {
+ font-family: KaTeX_SansSerif;
+}
+.katex .texttt {
+ font-family: KaTeX_Typewriter;
+}
+.katex .mathdefault {
+ font-family: KaTeX_Math;
+ font-style: italic;
+}
+.katex .mathit {
+ font-family: KaTeX_Main;
+ font-style: italic;
+}
+.katex .mathrm {
+ font-style: normal;
+}
+.katex .mathbf {
+ font-family: KaTeX_Main;
+ font-weight: 700;
+}
+.katex .boldsymbol {
+ font-family: KaTeX_Math;
+ font-weight: 700;
+ font-style: italic;
+}
+.katex .amsrm,
+.katex .mathbb,
+.katex .textbb {
+ font-family: KaTeX_AMS;
+}
+.katex .mathcal {
+ font-family: KaTeX_Caligraphic;
+}
+.katex .mathfrak,
+.katex .textfrak {
+ font-family: KaTeX_Fraktur;
+}
+.katex .mathtt {
+ font-family: KaTeX_Typewriter;
+}
+.katex .mathscr,
+.katex .textscr {
+ font-family: KaTeX_Script;
+}
+.katex .mathsf,
+.katex .textsf {
+ font-family: KaTeX_SansSerif;
+}
+.katex .mathboldsf,
+.katex .textboldsf {
+ font-family: KaTeX_SansSerif;
+ font-weight: 700;
+}
+.katex .mathitsf,
+.katex .textitsf {
+ font-family: KaTeX_SansSerif;
+ font-style: italic;
+}
+.katex .mainrm {
+ font-family: KaTeX_Main;
+ font-style: normal;
+}
+.katex .vlist-t {
+ display: inline-table;
+ table-layout: fixed;
+}
+.katex .vlist-r {
+ display: table-row;
+}
+.katex .vlist {
+ display: table-cell;
+ vertical-align: bottom;
+ position: relative;
+}
+.katex .vlist > span {
+ display: block;
+ height: 0;
+ position: relative;
+}
+.katex .vlist > span > span {
+ display: inline-block;
+}
+.katex .vlist > span > .pstrut {
+ overflow: hidden;
+ width: 0;
+}
+.katex .vlist-t2 {
+ margin-right: -2px;
+}
+.katex .vlist-s {
+ display: table-cell;
+ vertical-align: bottom;
+ font-size: 1px;
+ width: 2px;
+ min-width: 2px;
+}
+.katex .msupsub {
+ text-align: left;
+}
+.katex .mfrac > span > span {
+ text-align: center;
+}
+.katex .mfrac .frac-line {
+ display: inline-block;
+ width: 100%;
+ border-bottom-style: solid;
+}
+.katex .hdashline,
+.katex .hline,
+.katex .mfrac .frac-line,
+.katex .overline .overline-line,
+.katex .rule,
+.katex .underline .underline-line {
+ min-height: 1px;
+}
+.katex .mspace {
+ display: inline-block;
+}
+.katex .clap,
+.katex .llap,
+.katex .rlap {
+ width: 0;
+ position: relative;
+}
+.katex .clap > .inner,
+.katex .llap > .inner,
+.katex .rlap > .inner {
+ position: absolute;
+}
+.katex .clap > .fix,
+.katex .llap > .fix,
+.katex .rlap > .fix {
+ display: inline-block;
+}
+.katex .llap > .inner {
+ right: 0;
+}
+.katex .clap > .inner,
+.katex .rlap > .inner {
+ left: 0;
+}
+.katex .clap > .inner > span {
+ margin-left: -50%;
+ margin-right: 50%;
+}
+.katex .rule {
+ display: inline-block;
+ border: 0 solid;
+ position: relative;
+}
+.katex .hline,
+.katex .overline .overline-line,
+.katex .underline .underline-line {
+ display: inline-block;
+ width: 100%;
+ border-bottom-style: solid;
+}
+.katex .hdashline {
+ display: inline-block;
+ width: 100%;
+ border-bottom-style: dashed;
+}
+.katex .sqrt > .root {
+ margin-left: 0.27777778em;
+ margin-right: -0.55555556em;
+}
+.katex .fontsize-ensurer,
+.katex .sizing {
+ display: inline-block;
+}
+.katex .fontsize-ensurer.reset-size1.size1,
+.katex .sizing.reset-size1.size1 {
+ font-size: 1em;
+}
+.katex .fontsize-ensurer.reset-size1.size2,
+.katex .sizing.reset-size1.size2 {
+ font-size: 1.2em;
+}
+.katex .fontsize-ensurer.reset-size1.size3,
+.katex .sizing.reset-size1.size3 {
+ font-size: 1.4em;
+}
+.katex .fontsize-ensurer.reset-size1.size4,
+.katex .sizing.reset-size1.size4 {
+ font-size: 1.6em;
+}
+.katex .fontsize-ensurer.reset-size1.size5,
+.katex .sizing.reset-size1.size5 {
+ font-size: 1.8em;
+}
+.katex .fontsize-ensurer.reset-size1.size6,
+.katex .sizing.reset-size1.size6 {
+ font-size: 2em;
+}
+.katex .fontsize-ensurer.reset-size1.size7,
+.katex .sizing.reset-size1.size7 {
+ font-size: 2.4em;
+}
+.katex .fontsize-ensurer.reset-size1.size8,
+.katex .sizing.reset-size1.size8 {
+ font-size: 2.88em;
+}
+.katex .fontsize-ensurer.reset-size1.size9,
+.katex .sizing.reset-size1.size9 {
+ font-size: 3.456em;
+}
+.katex .fontsize-ensurer.reset-size1.size10,
+.katex .sizing.reset-size1.size10 {
+ font-size: 4.148em;
+}
+.katex .fontsize-ensurer.reset-size1.size11,
+.katex .sizing.reset-size1.size11 {
+ font-size: 4.976em;
+}
+.katex .fontsize-ensurer.reset-size2.size1,
+.katex .sizing.reset-size2.size1 {
+ font-size: 0.83333333em;
+}
+.katex .fontsize-ensurer.reset-size2.size2,
+.katex .sizing.reset-size2.size2 {
+ font-size: 1em;
+}
+.katex .fontsize-ensurer.reset-size2.size3,
+.katex .sizing.reset-size2.size3 {
+ font-size: 1.16666667em;
+}
+.katex .fontsize-ensurer.reset-size2.size4,
+.katex .sizing.reset-size2.size4 {
+ font-size: 1.33333333em;
+}
+.katex .fontsize-ensurer.reset-size2.size5,
+.katex .sizing.reset-size2.size5 {
+ font-size: 1.5em;
+}
+.katex .fontsize-ensurer.reset-size2.size6,
+.katex .sizing.reset-size2.size6 {
+ font-size: 1.66666667em;
+}
+.katex .fontsize-ensurer.reset-size2.size7,
+.katex .sizing.reset-size2.size7 {
+ font-size: 2em;
+}
+.katex .fontsize-ensurer.reset-size2.size8,
+.katex .sizing.reset-size2.size8 {
+ font-size: 2.4em;
+}
+.katex .fontsize-ensurer.reset-size2.size9,
+.katex .sizing.reset-size2.size9 {
+ font-size: 2.88em;
+}
+.katex .fontsize-ensurer.reset-size2.size10,
+.katex .sizing.reset-size2.size10 {
+ font-size: 3.45666667em;
+}
+.katex .fontsize-ensurer.reset-size2.size11,
+.katex .sizing.reset-size2.size11 {
+ font-size: 4.14666667em;
+}
+.katex .fontsize-ensurer.reset-size3.size1,
+.katex .sizing.reset-size3.size1 {
+ font-size: 0.71428571em;
+}
+.katex .fontsize-ensurer.reset-size3.size2,
+.katex .sizing.reset-size3.size2 {
+ font-size: 0.85714286em;
+}
+.katex .fontsize-ensurer.reset-size3.size3,
+.katex .sizing.reset-size3.size3 {
+ font-size: 1em;
+}
+.katex .fontsize-ensurer.reset-size3.size4,
+.katex .sizing.reset-size3.size4 {
+ font-size: 1.14285714em;
+}
+.katex .fontsize-ensurer.reset-size3.size5,
+.katex .sizing.reset-size3.size5 {
+ font-size: 1.28571429em;
+}
+.katex .fontsize-ensurer.reset-size3.size6,
+.katex .sizing.reset-size3.size6 {
+ font-size: 1.42857143em;
+}
+.katex .fontsize-ensurer.reset-size3.size7,
+.katex .sizing.reset-size3.size7 {
+ font-size: 1.71428571em;
+}
+.katex .fontsize-ensurer.reset-size3.size8,
+.katex .sizing.reset-size3.size8 {
+ font-size: 2.05714286em;
+}
+.katex .fontsize-ensurer.reset-size3.size9,
+.katex .sizing.reset-size3.size9 {
+ font-size: 2.46857143em;
+}
+.katex .fontsize-ensurer.reset-size3.size10,
+.katex .sizing.reset-size3.size10 {
+ font-size: 2.96285714em;
+}
+.katex .fontsize-ensurer.reset-size3.size11,
+.katex .sizing.reset-size3.size11 {
+ font-size: 3.55428571em;
+}
+.katex .fontsize-ensurer.reset-size4.size1,
+.katex .sizing.reset-size4.size1 {
+ font-size: 0.625em;
+}
+.katex .fontsize-ensurer.reset-size4.size2,
+.katex .sizing.reset-size4.size2 {
+ font-size: 0.75em;
+}
+.katex .fontsize-ensurer.reset-size4.size3,
+.katex .sizing.reset-size4.size3 {
+ font-size: 0.875em;
+}
+.katex .fontsize-ensurer.reset-size4.size4,
+.katex .sizing.reset-size4.size4 {
+ font-size: 1em;
+}
+.katex .fontsize-ensurer.reset-size4.size5,
+.katex .sizing.reset-size4.size5 {
+ font-size: 1.125em;
+}
+.katex .fontsize-ensurer.reset-size4.size6,
+.katex .sizing.reset-size4.size6 {
+ font-size: 1.25em;
+}
+.katex .fontsize-ensurer.reset-size4.size7,
+.katex .sizing.reset-size4.size7 {
+ font-size: 1.5em;
+}
+.katex .fontsize-ensurer.reset-size4.size8,
+.katex .sizing.reset-size4.size8 {
+ font-size: 1.8em;
+}
+.katex .fontsize-ensurer.reset-size4.size9,
+.katex .sizing.reset-size4.size9 {
+ font-size: 2.16em;
+}
+.katex .fontsize-ensurer.reset-size4.size10,
+.katex .sizing.reset-size4.size10 {
+ font-size: 2.5925em;
+}
+.katex .fontsize-ensurer.reset-size4.size11,
+.katex .sizing.reset-size4.size11 {
+ font-size: 3.11em;
+}
+.katex .fontsize-ensurer.reset-size5.size1,
+.katex .sizing.reset-size5.size1 {
+ font-size: 0.55555556em;
+}
+.katex .fontsize-ensurer.reset-size5.size2,
+.katex .sizing.reset-size5.size2 {
+ font-size: 0.66666667em;
+}
+.katex .fontsize-ensurer.reset-size5.size3,
+.katex .sizing.reset-size5.size3 {
+ font-size: 0.77777778em;
+}
+.katex .fontsize-ensurer.reset-size5.size4,
+.katex .sizing.reset-size5.size4 {
+ font-size: 0.88888889em;
+}
+.katex .fontsize-ensurer.reset-size5.size5,
+.katex .sizing.reset-size5.size5 {
+ font-size: 1em;
+}
+.katex .fontsize-ensurer.reset-size5.size6,
+.katex .sizing.reset-size5.size6 {
+ font-size: 1.11111111em;
+}
+.katex .fontsize-ensurer.reset-size5.size7,
+.katex .sizing.reset-size5.size7 {
+ font-size: 1.33333333em;
+}
+.katex .fontsize-ensurer.reset-size5.size8,
+.katex .sizing.reset-size5.size8 {
+ font-size: 1.6em;
+}
+.katex .fontsize-ensurer.reset-size5.size9,
+.katex .sizing.reset-size5.size9 {
+ font-size: 1.92em;
+}
+.katex .fontsize-ensurer.reset-size5.size10,
+.katex .sizing.reset-size5.size10 {
+ font-size: 2.30444444em;
+}
+.katex .fontsize-ensurer.reset-size5.size11,
+.katex .sizing.reset-size5.size11 {
+ font-size: 2.76444444em;
+}
+.katex .fontsize-ensurer.reset-size6.size1,
+.katex .sizing.reset-size6.size1 {
+ font-size: 0.5em;
+}
+.katex .fontsize-ensurer.reset-size6.size2,
+.katex .sizing.reset-size6.size2 {
+ font-size: 0.6em;
+}
+.katex .fontsize-ensurer.reset-size6.size3,
+.katex .sizing.reset-size6.size3 {
+ font-size: 0.7em;
+}
+.katex .fontsize-ensurer.reset-size6.size4,
+.katex .sizing.reset-size6.size4 {
+ font-size: 0.8em;
+}
+.katex .fontsize-ensurer.reset-size6.size5,
+.katex .sizing.reset-size6.size5 {
+ font-size: 0.9em;
+}
+.katex .fontsize-ensurer.reset-size6.size6,
+.katex .sizing.reset-size6.size6 {
+ font-size: 1em;
+}
+.katex .fontsize-ensurer.reset-size6.size7,
+.katex .sizing.reset-size6.size7 {
+ font-size: 1.2em;
+}
+.katex .fontsize-ensurer.reset-size6.size8,
+.katex .sizing.reset-size6.size8 {
+ font-size: 1.44em;
+}
+.katex .fontsize-ensurer.reset-size6.size9,
+.katex .sizing.reset-size6.size9 {
+ font-size: 1.728em;
+}
+.katex .fontsize-ensurer.reset-size6.size10,
+.katex .sizing.reset-size6.size10 {
+ font-size: 2.074em;
+}
+.katex .fontsize-ensurer.reset-size6.size11,
+.katex .sizing.reset-size6.size11 {
+ font-size: 2.488em;
+}
+.katex .fontsize-ensurer.reset-size7.size1,
+.katex .sizing.reset-size7.size1 {
+ font-size: 0.41666667em;
+}
+.katex .fontsize-ensurer.reset-size7.size2,
+.katex .sizing.reset-size7.size2 {
+ font-size: 0.5em;
+}
+.katex .fontsize-ensurer.reset-size7.size3,
+.katex .sizing.reset-size7.size3 {
+ font-size: 0.58333333em;
+}
+.katex .fontsize-ensurer.reset-size7.size4,
+.katex .sizing.reset-size7.size4 {
+ font-size: 0.66666667em;
+}
+.katex .fontsize-ensurer.reset-size7.size5,
+.katex .sizing.reset-size7.size5 {
+ font-size: 0.75em;
+}
+.katex .fontsize-ensurer.reset-size7.size6,
+.katex .sizing.reset-size7.size6 {
+ font-size: 0.83333333em;
+}
+.katex .fontsize-ensurer.reset-size7.size7,
+.katex .sizing.reset-size7.size7 {
+ font-size: 1em;
+}
+.katex .fontsize-ensurer.reset-size7.size8,
+.katex .sizing.reset-size7.size8 {
+ font-size: 1.2em;
+}
+.katex .fontsize-ensurer.reset-size7.size9,
+.katex .sizing.reset-size7.size9 {
+ font-size: 1.44em;
+}
+.katex .fontsize-ensurer.reset-size7.size10,
+.katex .sizing.reset-size7.size10 {
+ font-size: 1.72833333em;
+}
+.katex .fontsize-ensurer.reset-size7.size11,
+.katex .sizing.reset-size7.size11 {
+ font-size: 2.07333333em;
+}
+.katex .fontsize-ensurer.reset-size8.size1,
+.katex .sizing.reset-size8.size1 {
+ font-size: 0.34722222em;
+}
+.katex .fontsize-ensurer.reset-size8.size2,
+.katex .sizing.reset-size8.size2 {
+ font-size: 0.41666667em;
+}
+.katex .fontsize-ensurer.reset-size8.size3,
+.katex .sizing.reset-size8.size3 {
+ font-size: 0.48611111em;
+}
+.katex .fontsize-ensurer.reset-size8.size4,
+.katex .sizing.reset-size8.size4 {
+ font-size: 0.55555556em;
+}
+.katex .fontsize-ensurer.reset-size8.size5,
+.katex .sizing.reset-size8.size5 {
+ font-size: 0.625em;
+}
+.katex .fontsize-ensurer.reset-size8.size6,
+.katex .sizing.reset-size8.size6 {
+ font-size: 0.69444444em;
+}
+.katex .fontsize-ensurer.reset-size8.size7,
+.katex .sizing.reset-size8.size7 {
+ font-size: 0.83333333em;
+}
+.katex .fontsize-ensurer.reset-size8.size8,
+.katex .sizing.reset-size8.size8 {
+ font-size: 1em;
+}
+.katex .fontsize-ensurer.reset-size8.size9,
+.katex .sizing.reset-size8.size9 {
+ font-size: 1.2em;
+}
+.katex .fontsize-ensurer.reset-size8.size10,
+.katex .sizing.reset-size8.size10 {
+ font-size: 1.44027778em;
+}
+.katex .fontsize-ensurer.reset-size8.size11,
+.katex .sizing.reset-size8.size11 {
+ font-size: 1.72777778em;
+}
+.katex .fontsize-ensurer.reset-size9.size1,
+.katex .sizing.reset-size9.size1 {
+ font-size: 0.28935185em;
+}
+.katex .fontsize-ensurer.reset-size9.size2,
+.katex .sizing.reset-size9.size2 {
+ font-size: 0.34722222em;
+}
+.katex .fontsize-ensurer.reset-size9.size3,
+.katex .sizing.reset-size9.size3 {
+ font-size: 0.40509259em;
+}
+.katex .fontsize-ensurer.reset-size9.size4,
+.katex .sizing.reset-size9.size4 {
+ font-size: 0.46296296em;
+}
+.katex .fontsize-ensurer.reset-size9.size5,
+.katex .sizing.reset-size9.size5 {
+ font-size: 0.52083333em;
+}
+.katex .fontsize-ensurer.reset-size9.size6,
+.katex .sizing.reset-size9.size6 {
+ font-size: 0.5787037em;
+}
+.katex .fontsize-ensurer.reset-size9.size7,
+.katex .sizing.reset-size9.size7 {
+ font-size: 0.69444444em;
+}
+.katex .fontsize-ensurer.reset-size9.size8,
+.katex .sizing.reset-size9.size8 {
+ font-size: 0.83333333em;
+}
+.katex .fontsize-ensurer.reset-size9.size9,
+.katex .sizing.reset-size9.size9 {
+ font-size: 1em;
+}
+.katex .fontsize-ensurer.reset-size9.size10,
+.katex .sizing.reset-size9.size10 {
+ font-size: 1.20023148em;
+}
+.katex .fontsize-ensurer.reset-size9.size11,
+.katex .sizing.reset-size9.size11 {
+ font-size: 1.43981481em;
+}
+.katex .fontsize-ensurer.reset-size10.size1,
+.katex .sizing.reset-size10.size1 {
+ font-size: 0.24108004em;
+}
+.katex .fontsize-ensurer.reset-size10.size2,
+.katex .sizing.reset-size10.size2 {
+ font-size: 0.28929605em;
+}
+.katex .fontsize-ensurer.reset-size10.size3,
+.katex .sizing.reset-size10.size3 {
+ font-size: 0.33751205em;
+}
+.katex .fontsize-ensurer.reset-size10.size4,
+.katex .sizing.reset-size10.size4 {
+ font-size: 0.38572806em;
+}
+.katex .fontsize-ensurer.reset-size10.size5,
+.katex .sizing.reset-size10.size5 {
+ font-size: 0.43394407em;
+}
+.katex .fontsize-ensurer.reset-size10.size6,
+.katex .sizing.reset-size10.size6 {
+ font-size: 0.48216008em;
+}
+.katex .fontsize-ensurer.reset-size10.size7,
+.katex .sizing.reset-size10.size7 {
+ font-size: 0.57859209em;
+}
+.katex .fontsize-ensurer.reset-size10.size8,
+.katex .sizing.reset-size10.size8 {
+ font-size: 0.69431051em;
+}
+.katex .fontsize-ensurer.reset-size10.size9,
+.katex .sizing.reset-size10.size9 {
+ font-size: 0.83317261em;
+}
+.katex .fontsize-ensurer.reset-size10.size10,
+.katex .sizing.reset-size10.size10 {
+ font-size: 1em;
+}
+.katex .fontsize-ensurer.reset-size10.size11,
+.katex .sizing.reset-size10.size11 {
+ font-size: 1.19961427em;
+}
+.katex .fontsize-ensurer.reset-size11.size1,
+.katex .sizing.reset-size11.size1 {
+ font-size: 0.20096463em;
+}
+.katex .fontsize-ensurer.reset-size11.size2,
+.katex .sizing.reset-size11.size2 {
+ font-size: 0.24115756em;
+}
+.katex .fontsize-ensurer.reset-size11.size3,
+.katex .sizing.reset-size11.size3 {
+ font-size: 0.28135048em;
+}
+.katex .fontsize-ensurer.reset-size11.size4,
+.katex .sizing.reset-size11.size4 {
+ font-size: 0.32154341em;
+}
+.katex .fontsize-ensurer.reset-size11.size5,
+.katex .sizing.reset-size11.size5 {
+ font-size: 0.36173633em;
+}
+.katex .fontsize-ensurer.reset-size11.size6,
+.katex .sizing.reset-size11.size6 {
+ font-size: 0.40192926em;
+}
+.katex .fontsize-ensurer.reset-size11.size7,
+.katex .sizing.reset-size11.size7 {
+ font-size: 0.48231511em;
+}
+.katex .fontsize-ensurer.reset-size11.size8,
+.katex .sizing.reset-size11.size8 {
+ font-size: 0.57877814em;
+}
+.katex .fontsize-ensurer.reset-size11.size9,
+.katex .sizing.reset-size11.size9 {
+ font-size: 0.69453376em;
+}
+.katex .fontsize-ensurer.reset-size11.size10,
+.katex .sizing.reset-size11.size10 {
+ font-size: 0.83360129em;
+}
+.katex .fontsize-ensurer.reset-size11.size11,
+.katex .sizing.reset-size11.size11 {
+ font-size: 1em;
+}
+.katex .delimsizing.size1 {
+ font-family: KaTeX_Size1;
+}
+.katex .delimsizing.size2 {
+ font-family: KaTeX_Size2;
+}
+.katex .delimsizing.size3 {
+ font-family: KaTeX_Size3;
+}
+.katex .delimsizing.size4 {
+ font-family: KaTeX_Size4;
+}
+.katex .delimsizing.mult .delim-size1 > span {
+ font-family: KaTeX_Size1;
+}
+.katex .delimsizing.mult .delim-size4 > span {
+ font-family: KaTeX_Size4;
+}
+.katex .nulldelimiter {
+ display: inline-block;
+ width: 0.12em;
+}
+.katex .delimcenter,
+.katex .op-symbol {
+ position: relative;
+}
+.katex .op-symbol.small-op {
+ font-family: KaTeX_Size1;
+}
+.katex .op-symbol.large-op {
+ font-family: KaTeX_Size2;
+}
+.katex .op-limits > .vlist-t {
+ text-align: center;
+}
+.katex .accent > .vlist-t {
+ text-align: center;
+}
+.katex .accent .accent-body {
+ position: relative;
+}
+.katex .accent .accent-body:not(.accent-full) {
+ width: 0;
+}
+.katex .overlay {
+ display: block;
+}
+.katex .mtable .vertical-separator {
+ display: inline-block;
+ margin: 0 -0.025em;
+ border-right: 0.05em solid;
+ min-width: 1px;
+}
+.katex .mtable .vs-dashed {
+ border-right: 0.05em dashed;
+}
+.katex .mtable .arraycolsep {
+ display: inline-block;
+}
+.katex .mtable .col-align-c > .vlist-t {
+ text-align: center;
+}
+.katex .mtable .col-align-l > .vlist-t {
+ text-align: left;
+}
+.katex .mtable .col-align-r > .vlist-t {
+ text-align: right;
+}
+.katex .svg-align {
+ text-align: left;
+}
+.katex svg {
+ display: block;
+ position: absolute;
+ width: 100%;
+ height: inherit;
+ fill: currentColor;
+ stroke: currentColor;
+ fill-rule: nonzero;
+ fill-opacity: 1;
+ stroke-width: 1;
+ stroke-linecap: butt;
+ stroke-linejoin: miter;
+ stroke-miterlimit: 4;
+ stroke-dasharray: none;
+ stroke-dashoffset: 0;
+ stroke-opacity: 1;
+}
+.katex svg path {
+ stroke: none;
+}
+.katex img {
+ border-style: none;
+ min-width: 0;
+ min-height: 0;
+ max-width: none;
+ max-height: none;
+}
+.katex .stretchy {
+ width: 100%;
+ display: block;
+ position: relative;
+ overflow: hidden;
+}
+.katex .stretchy:after,
+.katex .stretchy:before {
+ content: "";
+}
+.katex .hide-tail {
+ width: 100%;
+ position: relative;
+ overflow: hidden;
+}
+.katex .halfarrow-left {
+ position: absolute;
+ left: 0;
+ width: 50.2%;
+ overflow: hidden;
+}
+.katex .halfarrow-right {
+ position: absolute;
+ right: 0;
+ width: 50.2%;
+ overflow: hidden;
+}
+.katex .brace-left {
+ position: absolute;
+ left: 0;
+ width: 25.1%;
+ overflow: hidden;
+}
+.katex .brace-center {
+ position: absolute;
+ left: 25%;
+ width: 50%;
+ overflow: hidden;
+}
+.katex .brace-right {
+ position: absolute;
+ right: 0;
+ width: 25.1%;
+ overflow: hidden;
+}
+.katex .x-arrow-pad {
+ padding: 0 0.5em;
+}
+.katex .mover,
+.katex .munder,
+.katex .x-arrow {
+ text-align: center;
+}
+.katex .boxpad {
+ padding: 0 0.3em;
+}
+.katex .fbox,
+.katex .fcolorbox {
+ box-sizing: border-box;
+ border: 0.04em solid;
+}
+.katex .cancel-pad {
+ padding: 0 0.2em;
+}
+.katex .cancel-lap {
+ margin-left: -0.2em;
+ margin-right: -0.2em;
+}
+.katex .sout {
+ border-bottom-style: solid;
+ border-bottom-width: 0.08em;
+}
+.katex-display {
+ display: block;
+ margin: 1em 0;
+ text-align: center;
+}
+.katex-display > .katex {
+ display: block;
+ text-align: center;
+ white-space: nowrap;
+}
+.katex-display > .katex > .katex-html {
+ display: block;
+ position: relative;
+}
+.katex-display > .katex > .katex-html > .tag {
+ position: absolute;
+ right: 0;
+}
+.katex-display.leqno > .katex > .katex-html > .tag {
+ left: 0;
+ right: auto;
+}
+.katex-display.fleqn > .katex {
+ text-align: left;
+}
diff --git a/public/css/style.css b/public/css/style.css
new file mode 100644
index 0000000..d1f1837
--- /dev/null
+++ b/public/css/style.css
@@ -0,0 +1,229 @@
+body {
+ color: #222;
+ background-color: #ffffff;
+ font-size: 20px;
+}
+
+body,
+code {
+ font-family: "Inconsolata", monospace;
+}
+
+h1,
+h2,
+h3 {
+ font-size: inherit;
+ display: inline;
+ font-weight: bold;
+}
+
+h2::before {
+ content: "## ";
+}
+
+h3::before {
+ content: "### ";
+}
+
+p {
+ margin: 30px 0;
+}
+
+h1 + p,
+h2 + p,
+h3 + p {
+ margin-top: 0;
+}
+
+p.listing {
+ margin-top: 0;
+}
+
+hr {
+ margin: 0;
+ border: none;
+}
+
+hr::after {
+ content: "---";
+ display: block;
+ text-align: center;
+}
+
+.main {
+ box-sizing: border-box;
+ width: 650px;
+ padding: 30px;
+ margin: 40px auto;
+}
+
+.blog-title {
+ border-bottom: 1px solid #ccc;
+ margin-bottom: 40px;
+}
+
+.article {
+ margin: 34px 0;
+ line-height: 1.6;
+}
+
+a,
+a:visited {
+ color: #9d261d;
+ text-decoration: none;
+}
+
+a:hover {
+ text-decoration: underline;
+}
+
+a:focus {
+ background-color: #9d261d;
+ color: #fff;
+ outline: none;
+ text-decoration: none;
+}
+
+.date {
+ float: right;
+ color: #767676;
+ font-style: italic;
+}
+
+.tweet {
+ float: right;
+}
+
+code {
+ background-color: #eeeeee;
+}
+
+pre {
+ border-left: 1px solid #d9d9d9;
+ position: relative;
+ left: -20px;
+ padding-left: 20px;
+ overflow-x: scroll;
+}
+
+pre code {
+ background-color: #ffffff;
+ display: block;
+}
+
+img {
+ border: 2px solid black;
+ width: 100%;
+}
+
+.flex {
+ display: -webkit-flex;
+ display: -moz-box;
+ display: -ms-flexbox;
+ display: flex;
+ width: 100%;
+
+ flex-flow: row wrap;
+ -webkit-justify-content: center;
+ justify-content: center;
+}
+
+.demo {
+ border: 2px solid #000;
+ box-sizing: border-box;
+ margin: 20px 0;
+ padding: 40px 0;
+}
+
+.flash-item {
+ animation: flash 8s linear 0s infinite;
+ -webkit-animation: flash 8s linear 0s infinite;
+ -moz-animation: flash 8s linear 0s infinite;
+ -o-animation: flash 8s linear 0s infinite;
+}
+
+.sr-only {
+ border: 0;
+ clip: rect(0, 0, 0, 0);
+ height: 1px;
+ margin: -1px;
+ overflow: hidden;
+ padding: 0;
+ position: absolute;
+ width: 1px;
+}
+
+@keyframes flash {
+ 0% {
+ opacity: 1;
+ }
+ 10% {
+ opacity: 0.1;
+ }
+ 20% {
+ opacity: 1;
+ }
+}
+@-webkit-keyframes flash {
+ 0% {
+ opacity: 1;
+ }
+ 10% {
+ opacity: 0.1;
+ }
+ 20% {
+ opacity: 1;
+ }
+}
+@-moz-keyframes flash {
+ 0% {
+ opacity: 1;
+ }
+ 10% {
+ opacity: 0.1;
+ }
+ 20% {
+ opacity: 1;
+ }
+}
+@-o-keyframes flash {
+ 0% {
+ opacity: 1;
+ }
+ 10% {
+ opacity: 0.1;
+ }
+ 20% {
+ opacity: 1;
+ }
+}
+
+@media (max-width: 480px) {
+ body {
+ font-size: 20px;
+ line-height: 1.4;
+ }
+
+ .main {
+ width: 100%;
+ padding: 10px;
+ margin-top: 0;
+ }
+
+ .article {
+ margin-top: 30px;
+ }
+
+ .date {
+ float: none;
+ display: block;
+ }
+
+ .sep {
+ display: none;
+ }
+
+ header a {
+ display: block;
+ }
+}
diff --git a/public/css/tomorrow.min.css b/public/css/tomorrow.min.css
new file mode 100644
index 0000000..b025a41
--- /dev/null
+++ b/public/css/tomorrow.min.css
@@ -0,0 +1,75 @@
+.hljs-comment {
+ color: #8e908c;
+}
+.hljs-variable,
+.hljs-attribute,
+.hljs-tag,
+.hljs-regexp,
+.ruby .hljs-constant,
+.xml .hljs-tag .hljs-title,
+.xml .hljs-pi,
+.xml .hljs-doctype,
+.html .hljs-doctype,
+.css .hljs-id,
+.css .hljs-class,
+.css .hljs-pseudo {
+ color: #c82829;
+}
+.hljs-number,
+.hljs-preprocessor,
+.hljs-pragma,
+.hljs-built_in,
+.hljs-literal,
+.hljs-params,
+.language-prolog .hljs-symbol,
+.hljs-constant {
+ color: #f5871f;
+}
+.ruby .hljs-class .hljs-title,
+.css .hljs-rule .hljs-attribute {
+ color: #eab700;
+}
+.hljs-string,
+.hljs-value,
+.hljs-inheritance,
+.hljs-header,
+.hljs-name,
+.ruby .hljs-symbol,
+.xml .hljs-cdata {
+ color: #718c00;
+}
+.hljs-title,
+.css .hljs-hexcolor {
+ color: #3e999f;
+}
+.hljs-function,
+.python .hljs-decorator,
+.python .hljs-title,
+.ruby .hljs-function .hljs-title,
+.ruby .hljs-title .hljs-keyword,
+.perl .hljs-sub,
+.javascript .hljs-title,
+.coffeescript .hljs-title {
+ color: #4271ae;
+}
+.hljs-keyword,
+.javascript .hljs-function {
+ color: #8959a8;
+}
+.hljs {
+ display: block;
+ overflow-x: auto;
+ background: white;
+ color: #4d4d4c;
+ padding: 0.5em;
+ -webkit-text-size-adjust: none;
+}
+.coffeescript .javascript,
+.javascript .xml,
+.tex .hljs-formula,
+.xml .javascript,
+.xml .vbscript,
+.xml .css,
+.xml .hljs-cdata {
+ opacity: 0.5;
+}
diff --git a/public/img/a11y-invite.png b/public/img/a11y-invite.png
new file mode 100644
index 0000000..f52ad6f
--- /dev/null
+++ b/public/img/a11y-invite.png
Binary files differ
diff --git a/public/img/accessicademy.png b/public/img/accessicademy.png
new file mode 100644
index 0000000..c347e27
--- /dev/null
+++ b/public/img/accessicademy.png
Binary files differ
diff --git a/public/img/ama.png b/public/img/ama.png
new file mode 100644
index 0000000..4526b20
--- /dev/null
+++ b/public/img/ama.png
Binary files differ
diff --git a/public/img/custom-filters.png b/public/img/custom-filters.png
new file mode 100644
index 0000000..b3e7dfe
--- /dev/null
+++ b/public/img/custom-filters.png
Binary files differ
diff --git a/public/img/digits.PNG b/public/img/digits.PNG
new file mode 100644
index 0000000..655ea07
--- /dev/null
+++ b/public/img/digits.PNG
Binary files differ
diff --git a/public/img/extension-settings.png b/public/img/extension-settings.png
new file mode 100644
index 0000000..f2a1a9f
--- /dev/null
+++ b/public/img/extension-settings.png
Binary files differ
diff --git a/public/img/github-spam.png b/public/img/github-spam.png
new file mode 100644
index 0000000..89a3d49
--- /dev/null
+++ b/public/img/github-spam.png
Binary files differ
diff --git a/public/img/greasemonkey-confirm.png b/public/img/greasemonkey-confirm.png
new file mode 100644
index 0000000..b05b5b3
--- /dev/null
+++ b/public/img/greasemonkey-confirm.png
Binary files differ
diff --git a/public/img/greasemonkey.png b/public/img/greasemonkey.png
new file mode 100644
index 0000000..b62db8d
--- /dev/null
+++ b/public/img/greasemonkey.png
Binary files differ
diff --git a/public/img/spam.png b/public/img/spam.png
new file mode 100644
index 0000000..d3992c7
--- /dev/null
+++ b/public/img/spam.png
Binary files differ
diff --git a/public/img/tota11y.png b/public/img/tota11y.png
new file mode 100644
index 0000000..d787d89
--- /dev/null
+++ b/public/img/tota11y.png
Binary files differ
diff --git a/public/img/voiceover.png b/public/img/voiceover.png
new file mode 100644
index 0000000..afa7ff3
--- /dev/null
+++ b/public/img/voiceover.png
Binary files differ
diff --git a/public/img/window.png b/public/img/window.png
new file mode 100644
index 0000000..da10689
--- /dev/null
+++ b/public/img/window.png
Binary files differ
diff --git a/public/js/bowling.js b/public/js/bowling.js
new file mode 100644
index 0000000..e65bf13
--- /dev/null
+++ b/public/js/bowling.js
@@ -0,0 +1,402 @@
+document.addEventListener("DOMContentLoaded", function() {
+ var graph = document.getElementById("graph")
+ var stats = document.getElementById("stats")
+ var scoreboard = document.getElementById("scoreboard")
+
+ function generateEmptyGame() {
+ var frames = []
+ for (var i = 0; i < 9; i++) {
+ frames.push({
+ throws: [null, null],
+ score: null,
+ })
+ }
+
+ frames.push({
+ throws: [null, null, null],
+ score: null,
+ })
+
+ return {
+ frames: frames,
+ currentFrame: 0,
+ isComplete: false,
+ }
+ }
+
+ function scoreGame(game) {
+ var thisFrameScore
+ for (var i = 0; i < 10; i++) {
+ if (game.frames[i].throws[0] === null) {
+ break
+ }
+
+ if (i === 9) {
+ var firstThrow = game.frames[i].throws[0] || 0
+ var secondThrow = game.frames[i].throws[1] || 0
+ var thirdThrow = game.frames[i].throws[2] || 0
+
+ thisFrameScore = firstThrow + secondThrow + thirdThrow
+ } else {
+ // Fetch the next two throws in case we get a mark
+ var firstBonusThrow = game.frames[i + 1].throws[0] || 0
+ var secondBonusThrow
+ if (firstBonusThrow === 10) {
+ // If we're in the 9th frame, pick the second throw of
+ // the next frame as the 10th frame can have multiple
+ // strikes
+ if (i === 8) {
+ secondBonusThrow = game.frames[i + 1].throws[1] || 0
+ } else {
+ secondBonusThrow = game.frames[i + 2].throws[0] || 0
+ }
+ } else {
+ secondBonusThrow = game.frames[i + 1].throws[1] || 0
+ }
+
+ var firstThrow = game.frames[i].throws[0] || 0
+ var secondThrow = game.frames[i].throws[1] || 0
+
+ if (firstThrow === 10) {
+ thisFrameScore = 10 + firstBonusThrow + secondBonusThrow
+ } else if (firstThrow + secondThrow === 10) {
+ thisFrameScore = 10 + firstBonusThrow
+ } else {
+ thisFrameScore = firstThrow + secondThrow
+ }
+ }
+
+ if (i === 0) {
+ game.frames[i].score = thisFrameScore
+ } else {
+ game.frames[i].score = game.frames[i - 1].score + thisFrameScore
+ }
+ }
+ }
+
+ function generateThrow(game) {
+ var isNewFrame = game.frames[game.currentFrame].throws[0] === null
+
+ if (isNewFrame) {
+ var firstThrow = Math.floor(Math.random() * 11)
+ game.frames[game.currentFrame].throws[0] = firstThrow
+
+ if (firstThrow === 10 && game.currentFrame !== 9) {
+ game.currentFrame++
+ }
+ } else {
+ if (game.currentFrame !== 9) {
+ var lastThrow = game.frames[game.currentFrame].throws[0]
+ var nextThrow = Math.floor(Math.random() * (10 - lastThrow + 1))
+
+ game.frames[game.currentFrame].throws[1] = nextThrow
+
+ if (game.currentFrame !== 9) {
+ game.currentFrame++
+ }
+ } else {
+ var firstThrow = game.frames[game.currentFrame].throws[0]
+ var secondThrow = game.frames[game.currentFrame].throws[1]
+
+ if (firstThrow === 10 && secondThrow === null) {
+ var secondThrow = Math.floor(Math.random() * 11)
+ game.frames[game.currentFrame].throws[1] = secondThrow
+ } else if (secondThrow === null) {
+ var secondThrow = Math.floor(Math.random() * (10 - firstThrow + 1))
+
+ game.frames[game.currentFrame].throws[1] = secondThrow
+
+ if (firstThrow + secondThrow !== 10) {
+ game.isComplete = true
+ }
+ } else if (firstThrow === 10 && secondThrow === 10) {
+ var thirdThrow = Math.floor(Math.random() * 11)
+ game.frames[game.currentFrame].throws[2] = thirdThrow
+ game.isComplete = true
+ } else if (firstThrow === 10) {
+ var thirdThrow = Math.floor(Math.random() * (10 - secondThrow + 1))
+
+ game.frames[game.currentFrame].throws[2] = thirdThrow
+ game.isComplete = true
+ } else if (firstThrow + secondThrow === 10) {
+ var thirdThrow = Math.floor(Math.random() * 11)
+ game.frames[game.currentFrame].throws[2] = thirdThrow
+ game.isComplete = true
+ }
+ }
+ }
+ }
+
+ function renderScoreBoard(game) {
+ var frame
+ scoreboard.innerHTML = ""
+
+ var firstHalf = document.createElement("div")
+ firstHalf.classList.add("scoreboard-half")
+
+ var secondHalf = document.createElement("div")
+ secondHalf.classList.add("scoreboard-half")
+
+ for (var i = 0; i < 5; i++) {
+ frame = game.frames[i]
+
+ firstHalf.appendChild(renderFrame(frame.score, frame.throws, i === 9))
+ }
+
+ var mobileSpacer = document.createElement("div")
+ mobileSpacer.classList.add("mobile-spacer")
+ firstHalf.appendChild(mobileSpacer)
+
+ for (var i = 5; i < 10; i++) {
+ frame = game.frames[i]
+
+ secondHalf.appendChild(renderFrame(frame.score, frame.throws, i === 9))
+ }
+
+ scoreboard.appendChild(firstHalf)
+ scoreboard.appendChild(secondHalf)
+ }
+
+ function throwToString(points) {
+ if (points === null) {
+ return " "
+ } else if (points === 10) {
+ return "X"
+ } else if (points === 0) {
+ return "-"
+ } else {
+ return points + ""
+ }
+ }
+
+ // Not the 10th though!
+ function frameToString(throws) {
+ if (throws[0] === 10) {
+ return ["X", ""]
+ } else if (throws[0] + throws[1] === 10) {
+ return [throwToString(throws[0]), "/"]
+ } else {
+ return throws.map(throwToString)
+ }
+ }
+
+ function renderFrame(score, throws, isTenth) {
+ var contents
+ if (isTenth) {
+ // 10th frame has weird logic
+ if (throws === undefined) {
+ contents = ["", "", ""]
+
+ // Two strikes = XX[third throw]
+ } else if (throws[0] === 10 && throws[1] === 10) {
+ contents = [
+ throwToString(10),
+ throwToString(10),
+ throwToString(throws[2]),
+ ]
+
+ // X[normal frame]
+ } else if (throws[0] === 10) {
+ contents = [throwToString(10)].concat(frameToString(throws.slice(1)))
+
+ // [normal frame][bonus throw]
+ } else if (throws[0] + throws[1] === 10) {
+ contents = frameToString(throws.slice(0, 2)).concat([
+ throwToString(throws[2]),
+ ])
+ } else {
+ contents = frameToString(throws)
+ }
+
+ // Make contents 3 items long... lol
+ if (contents.length < 3) {
+ contents.push("")
+ }
+
+ if (contents.length < 3) {
+ contents.push("")
+ }
+ } else {
+ contents = frameToString(throws)
+
+ if (contents.length < 2) {
+ contents.push("")
+ }
+ }
+
+ var frame = document.createElement("div")
+ frame.classList.add("frame")
+
+ contents.forEach(function(formattedThrow) {
+ var square = document.createElement("div")
+ square.classList.add("square")
+ square.innerHTML = formattedThrow
+
+ frame.appendChild(square)
+ })
+
+ var scoreDiv = document.createElement("div")
+ scoreDiv.classList.add("score")
+ scoreDiv.innerHTML = score
+
+ frame.appendChild(scoreDiv)
+
+ return frame
+ }
+
+ function renderGraph(graphDiv) {
+ graphDiv.innerHTML = ""
+
+ var distribution = []
+ for (var i = 0; i <= 300; i++) {
+ distribution[i] = scores[i] || 0
+ }
+ var highestPopulation = Math.max.apply(null, distribution)
+
+ var bar
+ for (var i = 0; i <= 300; i++) {
+ bar = document.createElement("div")
+ bar.classList.add("bar")
+ bar.style.height = ((scores[i] || 0) / highestPopulation) * 100 + "%"
+ graphDiv.appendChild(bar)
+ }
+ }
+
+ function renderStats(statsDiv) {
+ var distribution = []
+ for (var i = 0; i <= 300; i++) {
+ distribution[i] = scores[i] || 0
+ }
+
+ var numGames = distribution.reduce(function(a, b) {
+ return a + b
+ })
+
+ var allGames = []
+ var totalPins = 0
+ var minScore, maxScore
+
+ var mode = 0,
+ modeIndex
+
+ for (var i = 0; i <= 300; i++) {
+ for (var j = 0; j < distribution[i]; j++) {
+ allGames.push(i)
+ totalPins += i
+ }
+
+ if (distribution[i] > 0) {
+ maxScore = i
+
+ if (minScore === undefined) {
+ minScore = i
+ }
+ }
+
+ if (distribution[i] > mode) {
+ mode = distribution[i]
+ modeIndex = i
+ }
+ }
+
+ var mean = Math.floor((totalPins / numGames) * 100) / 100
+
+ // TODO: Odd/even
+ var median
+ if (allGames.length % 2 === 1) {
+ median = allGames[Math.floor(allGames.length / 2)]
+ } else {
+ median =
+ (allGames[Math.floor(allGames.length / 2)] +
+ allGames[Math.floor(allGames.length / 2) - 1]) /
+ 2
+ }
+
+ statsDiv.innerHTML =
+ "<div>" +
+ [
+ '<span class="stats-label">Games:</span><span> ' + numGames + "</span>",
+ '<span class="stats-label">Min:</span><span> ' + minScore + "</span>",
+ '<span class="stats-label">Max:</span><span> ' + maxScore + "</span>",
+ '<span class="stats-label">Mean:</span><span> ' + mean + "</span>",
+ '<span class="stats-label">Median:</span><span> ' + median + "</span>",
+ '<span class="stats-label">Mode:</span><span> ' +
+ modeIndex +
+ " (" +
+ mode +
+ " games)</span>",
+ ].join("</div><div>") +
+ "</div>"
+ }
+
+ var scores = []
+ var game = generateEmptyGame()
+ renderScoreBoard(game)
+
+ var throwButton = document.getElementById("throw")
+ var gameButton = document.getElementById("game")
+ var manyGamesButton = document.getElementById("many-games")
+
+ function handleThrowClick(e) {
+ e.preventDefault()
+
+ if (game.isComplete) {
+ game = generateEmptyGame()
+ }
+
+ generateThrow(game)
+ scoreGame(game)
+ renderScoreBoard(game)
+
+ if (game.isComplete) {
+ var score = game.frames[9].score
+ scores[score] = (scores[score] || 0) + 1
+ renderGraph(graph)
+ renderStats(stats)
+ }
+ }
+
+ function simulateGame() {
+ game = generateEmptyGame()
+ while (!game.isComplete) {
+ generateThrow(game)
+ }
+
+ scoreGame(game)
+ renderScoreBoard(game)
+
+ var score = game.frames[9].score
+ scores[score] = (scores[score] || 0) + 1
+ renderGraph(graph)
+ }
+
+ function handleGameClick(e) {
+ e.preventDefault()
+ simulateGame()
+ renderStats(stats)
+ }
+
+ function handleManyClick(e) {
+ e.preventDefault()
+ var simulations = 0
+
+ ;(function run() {
+ simulations++
+ simulateGame()
+ renderStats(stats)
+
+ if (simulations < 100) {
+ setTimeout(run, 5)
+ }
+ })()
+ }
+
+ throwButton.addEventListener("click", handleThrowClick)
+ throwButton.addEventListener("touchend", handleThrowClick)
+
+ gameButton.addEventListener("click", handleGameClick)
+ gameButton.addEventListener("touchend", handleGameClick)
+
+ manyGamesButton.addEventListener("click", handleManyClick)
+ manyGamesButton.addEventListener("touchend", handleManyClick)
+})
diff --git a/public/js/color-blindness.js b/public/js/color-blindness.js
new file mode 100644
index 0000000..306c98f
--- /dev/null
+++ b/public/js/color-blindness.js
@@ -0,0 +1,30 @@
+var values = {
+ red: 100,
+ green: 100,
+ blue: 100,
+}
+
+function renderLabel(control) {
+ document.getElementById(control + "-value").innerText =
+ "(" + values[control] + ")"
+}
+
+function renderFilter() {
+ var matrix = [
+ [values.red / 100, 0, 0, 0, 0].join(","),
+ [0, values.green / 100, 0, 0, 0].join(","),
+ [0, 0, values.blue / 100, 0, 0].join(","),
+ [0, 0, 0, 1, 0].join(","),
+ ].join("\n")
+
+ document.getElementById("color-matrix").setAttribute("values", matrix)
+}
+
+;["red", "green", "blue"].forEach(function(control) {
+ document.getElementById(control).addEventListener("input", function(e) {
+ values[control] = e.target.value
+
+ renderLabel(control)
+ renderFilter()
+ })
+})
diff --git a/public/js/demos/clicking.js b/public/js/demos/clicking.js
new file mode 100644
index 0000000..4e2e191
--- /dev/null
+++ b/public/js/demos/clicking.js
@@ -0,0 +1,24 @@
+setInterval(function() {
+ var examples = [].slice.call(document.querySelectorAll(".a11y-onclick"))
+ var outputs = [].slice.call(
+ document.querySelectorAll(".a11y-onclick:not(.no-output) .output")
+ )
+
+ examples.forEach(function(el) {
+ el.classList.add("active")
+ })
+
+ outputs.forEach(function(o) {
+ o.innerHTML = "Clicked!"
+ })
+
+ setTimeout(function() {
+ examples.forEach(function(el) {
+ el.classList.remove("active")
+ })
+
+ outputs.forEach(function(o) {
+ o.innerHTML = " "
+ })
+ }, 1000)
+}, 2500)
diff --git a/public/js/demos/hover.js b/public/js/demos/hover.js
new file mode 100644
index 0000000..57d9f3c
--- /dev/null
+++ b/public/js/demos/hover.js
@@ -0,0 +1,42 @@
+var highlightIntervals = [
+ {
+ on: [550, 3900],
+ off: [1400, 4150],
+ },
+ {
+ on: [1700, 3500],
+ off: [1900, 3800],
+ },
+ {
+ on: [2000],
+ off: [3400],
+ },
+]
+
+function highlightSequence() {
+ var examples = [].slice.call(
+ document.querySelectorAll(".hover-a11y:not(.no-active)")
+ )
+
+ examples.forEach(function(example) {
+ console.log(example)
+ var links = [].slice.call(example.querySelectorAll(".button"))
+
+ highlightIntervals.forEach(function(timers, i) {
+ timers.on.forEach(function(ms) {
+ setTimeout(function() {
+ links[i].classList.add("active")
+ }, ms)
+ })
+
+ timers.off.forEach(function(ms) {
+ setTimeout(function() {
+ links[i].classList.remove("active")
+ }, ms)
+ })
+ })
+ })
+}
+
+setInterval(highlightSequence, 6000)
+highlightSequence() // call once to start
diff --git a/public/js/flasher.js b/public/js/flasher.js
new file mode 100644
index 0000000..3c82f28
--- /dev/null
+++ b/public/js/flasher.js
@@ -0,0 +1,16 @@
+var flashEl = document.querySelector(".flash")
+var flashText = flashEl.innerHTML
+var flashItems, i
+
+flashEl.innerHTML = ""
+for (var i = 0; i < flashText.length; i++) {
+ item = document.createElement("span")
+ item.innerHTML = flashText[i]
+ item.className = "flash-item"
+
+ // animation delay
+ item.style.animationDelay = item.style.webkitAnimationDelay = item.style.mozAnimationDelay = item.style.oAnimationDelay =
+ 1 + 0.1 * i + "s"
+
+ flashEl.appendChild(item)
+}
diff --git a/public/js/fraction.min.js b/public/js/fraction.min.js
new file mode 100644
index 0000000..2b6a66b
--- /dev/null
+++ b/public/js/fraction.min.js
@@ -0,0 +1,18 @@
+/*
+Fraction.js v4.0.12 09/09/2015
+http://www.xarg.org/2014/03/rational-numbers-in-javascript/
+
+Copyright (c) 2015, Robert Eisele ([email protected])
+Dual licensed under the MIT or GPL Version 2 licenses.
+*/
+(function(x){function l(a,b){var c=0,k=1,h=1,e=0,u=0,l=0,p=1,r=1,f=0,g=1,t=1,q=1;if(void 0!==a&&null!==a)if(void 0!==b)c=a,k=b,h=c*k;else switch(typeof a){case "object":"d"in a&&"n"in a?(c=a.n,k=a.d,"s"in a&&(c*=a.s)):0 in a?(c=a[0],1 in a&&(k=a[1])):v();h=c*k;break;case "number":0>a&&(h=a,a=-a);if(0===a%1)c=a;else if(0<a){1<=a&&(r=Math.pow(10,Math.floor(1+Math.log(a)/Math.LN10)),a/=r);for(;1E7>=g&&1E7>=q;)if(c=(f+t)/(g+q),a===c){1E7>=g+q?(c=f+t,k=g+q):q>g?(c=t,k=q):(c=f,k=g);break}else a>c?(f+=t,
+ g+=q):(t+=f,q+=g),1E7<g?(c=t,k=q):(c=f,k=g);c*=r}else if(isNaN(a)||isNaN(b))k=c=NaN;break;case "string":g=a.match(/\d+|./g);null===g&&v();"-"===g[f]?(h=-1,f++):"+"===g[f]&&f++;if(g.length===f+1)u=n(g[f++],h);else if("."===g[f+1]||"."===g[f]){"."!==g[f]&&(e=n(g[f++],h));f++;if(f+1===g.length||"("===g[f+1]&&")"===g[f+3]||"'"===g[f+1]&&"'"===g[f+3])u=n(g[f],h),p=Math.pow(10,g[f].length),f++;if("("===g[f]&&")"===g[f+2]||"'"===g[f]&&"'"===g[f+2])l=n(g[f+1],h),r=Math.pow(10,g[f+1].length)-1,f+=3}else"/"===
+ g[f+1]||":"===g[f+1]?(u=n(g[f],h),p=n(g[f+2],1),f+=3):"/"===g[f+3]&&" "===g[f+1]&&(e=n(g[f],h),u=n(g[f+2],h),p=n(g[f+4],1),f+=5);if(g.length<=f){k=p*r;h=c=l+k*e+r*u;break}default:v()}if(0===k)throw new y;d.s=0>h?-1:1;d.n=Math.abs(c);d.d=Math.abs(k)}function w(a){function b(){var b=Error.apply(this,arguments);b.name=this.name=a;this.stack=b.stack;this.message=b.message}function c(){}c.prototype=Error.prototype;b.prototype=new c;return b}function n(a,b){isNaN(a=parseInt(a,10))&&v();return a*b}function v(){throw new z;
+ }function p(a,b){if(!a)return b;if(!b)return a;for(;;){a%=b;if(!a)return b;b%=a;if(!b)return a}}function e(a,b){if(!(this instanceof e))return new e(a,b);l(a,b);a=e.REDUCE?p(d.d,d.n):1;this.s=d.s;this.n=d.n/a;this.d=d.d/a}var d={s:1,n:0,d:1},y=e.DivisionByZero=w("DivisionByZero"),z=e.InvalidParameter=w("InvalidParameter");e.REDUCE=1;e.prototype={s:1,n:0,d:1,abs:function(){return new e(this.n,this.d)},neg:function(){return new e(-this.s*this.n,this.d)},add:function(a,b){l(a,b);return new e(this.s*
+ this.n*d.d+d.s*this.d*d.n,this.d*d.d)},sub:function(a,b){l(a,b);return new e(this.s*this.n*d.d-d.s*this.d*d.n,this.d*d.d)},mul:function(a,b){l(a,b);return new e(this.s*d.s*this.n*d.n,this.d*d.d)},div:function(a,b){l(a,b);return new e(this.s*d.s*this.n*d.d,this.d*d.n)},clone:function(){return new e(this)},mod:function(a,b){if(isNaN(this.n)||isNaN(this.d))return new e(NaN);if(void 0===a)return new e(this.s*this.n%this.d,1);l(a,b);0===d.n&&0===this.d&&e(0,0);return new e(this.s*d.d*this.n%(d.n*this.d),
+ d.d*this.d)},gcd:function(a,b){l(a,b);return new e(p(d.n,this.n)*p(d.d,this.d),d.d*this.d)},lcm:function(a,b){l(a,b);return 0===d.n&&0===this.n?new e:new e(d.n*this.n,p(d.n,this.n)*p(d.d,this.d))},ceil:function(a){a=Math.pow(10,a||0);return isNaN(this.n)||isNaN(this.d)?new e(NaN):new e(Math.ceil(a*this.s*this.n/this.d),a)},floor:function(a){a=Math.pow(10,a||0);return isNaN(this.n)||isNaN(this.d)?new e(NaN):new e(Math.floor(a*this.s*this.n/this.d),a)},round:function(a){a=Math.pow(10,a||0);return isNaN(this.n)||
+ isNaN(this.d)?new e(NaN):new e(Math.round(a*this.s*this.n/this.d),a)},inverse:function(){return new e(this.s*this.d,this.n)},pow:function(a){return 0>a?new e(Math.pow(this.s*this.d,-a),Math.pow(this.n,-a)):new e(Math.pow(this.s*this.n,a),Math.pow(this.d,a))},equals:function(a,b){l(a,b);return this.s*this.n*d.d===d.s*d.n*this.d},compare:function(a,b){l(a,b);var c=this.s*this.n*d.d-d.s*d.n*this.d;return(0<c)-(0>c)},simplify:function(a){function b(a){return 1===a.length?new e(a[0]):b(a.slice(1)).inverse().add(a[0])}
+ if(isNaN(this.n)||isNaN(this.d))return this;var c=this.abs().toContinued();a=a||.001;for(var k=0;k<c.length;k++){var h=b(c.slice(0,k+1));if(h.sub(this.abs()).abs().valueOf()<a)return h.mul(this.s)}return this},divisible:function(a,b){l(a,b);return!(!(d.n*this.d)||this.n*d.d%(d.n*this.d))},valueOf:function(){return this.s*this.n/this.d},toFraction:function(a){var b,c="",k=this.n,e=this.d;0>this.s&&(c+="-");1===e?c+=k:(a&&0<(b=Math.floor(k/e))&&(c=c+b+" ",k%=e),c=c+k+"/",c+=e);return c},toLatex:function(a){var b,
+ c="",e=this.n,d=this.d;0>this.s&&(c+="-");1===d?c+=e:(a&&0<(b=Math.floor(e/d))&&(c+=b,e%=d),c=c+"\\frac{"+e+"}{"+d,c+="}");return c},toContinued:function(){var a=this.n,b=this.d,c=[];if(isNaN(this.n)||isNaN(this.d))return c;do{c.push(Math.floor(a/b));var e=a%b;a=b;b=e}while(1!==a);return c},toString:function(a){var b=this.n,c=this.d;if(isNaN(b)||isNaN(c))return"NaN";if(!e.REDUCE){var d=p(b,c);b/=d;c/=d}a:{for(d=c;0===d%2;d/=2);for(;0===d%5;d/=5);if(1===d)d=0;else{for(var h=10%d,m=1;1!==h;m++)if(h=
+ 10*h%d,2E3<m){d=0;break a}d=m}}a:{h=1;m=10;for(var l=d,n=1;0<l;m=m*m%c,l>>=1)l&1&&(n=n*m%c);m=n;for(l=0;300>l;l++){if(h===m){m=l;break a}h=10*h%c;m=10*m%c}m=0}h=-1===this.s?"-":"";h+=b/c|0;(b=b%c*10)&&(h+=".");if(d){for(a=m;a--;)h+=b/c|0,b%=c,b*=10;h+="(";for(a=d;a--;)h+=b/c|0,b%=c,b*=10;h+=")"}else for(a=a||15;b&&a--;)h+=b/c|0,b%=c,b*=10;return h}};"function"===typeof define&&define.amd?define([],function(){return e}):"object"===typeof exports?(Object.defineProperty(exports,"__esModule",{value:!0}),
+ e["default"]=e,e.Fraction=e,module.exports=e):x.Fraction=e})(this);
diff --git a/save-32x32.js b/save-32x32.js
new file mode 100644
index 0000000..5cef273
--- /dev/null
+++ b/save-32x32.js
@@ -0,0 +1,24 @@
+const exec = require("child_process").exec;
+
+const command = [
+ "cd 32x32",
+ "npm install",
+ "npm run build",
+ "mv build/static ../output/", // HACK - move static first
+ "mv build ../output/32x32",
+].join(" && ");
+
+function save32x32() {
+ return new Promise((resolve, reject) => {
+ console.log(command);
+ exec(command, (err, stdout, stderr) => {
+ if (err || stderr) {
+ return reject(err || stderr);
+ }
+
+ resolve(stdout);
+ });
+ });
+}
+
+module.exports = save32x32;
diff --git a/save-article.js b/save-article.js
new file mode 100644
index 0000000..63145a2
--- /dev/null
+++ b/save-article.js
@@ -0,0 +1,40 @@
+const ejs = require("ejs");
+const fs = require("fs");
+const mkdirp = require("mkdirp");
+const path = require("path");
+
+function saveArticle(article) {
+ return new Promise((resolve, reject) => {
+ fs.readFile("templates/article.ejs", (err, articleTemplate) => {
+ if (err) { return reject(err); }
+
+ fs.readFile("templates/layout.ejs", (err, layoutTemplate) => {
+ if (err) { return reject(err); }
+
+ const articleBodyHTML = ejs.render(articleTemplate.toString(), article);
+ const articleHTML = ejs.render(layoutTemplate.toString(), {
+ title: article.title + " | Fuwn",
+ body: articleBodyHTML,
+ description: article.description || article.summary,
+ });
+
+ const articlePath = path.join("output", article.route);
+
+ mkdirp(articlePath, err => {
+ if (err) { return reject(err); }
+
+ fs.writeFile(
+ path.join(articlePath, "index.html"),
+ articleHTML,
+ err => {
+ if (err) { return reject(err); }
+ resolve("ok");
+ }
+ );
+ });
+ });
+ });
+ });
+}
+
+module.exports = saveArticle;
diff --git a/save-index.js b/save-index.js
new file mode 100644
index 0000000..b914eb6
--- /dev/null
+++ b/save-index.js
@@ -0,0 +1,37 @@
+const ejs = require("ejs");
+const fs = require("fs");
+
+function saveIndex(articles, outputFile, title, banner) {
+ articles.sort((a, b) => {
+ return (b.rawDate || 0) - (a.rawDate || 0);
+ });
+
+ return new Promise((resolve, reject) => {
+ fs.readFile("templates/index.ejs", (err, data) => {
+ if (err) { return reject(err); }
+
+ const indexBodyHTML = ejs.render(data.toString(), {
+ articles: articles.filter(article => {
+ return !article.hidden;
+ }),
+ banner,
+ });
+
+ fs.readFile("templates/layout.ejs", (err, data) => {
+ const indexHTML = ejs.render(data.toString(), {
+ title: title || "blog | fuwn",
+ description: "fuwn, a naritive through my struggles",
+ body: indexBodyHTML,
+ });
+
+ fs.writeFile(outputFile, indexHTML, err => {
+ if (err) { return reject(err); }
+
+ resolve("ok!");
+ });
+ });
+ });
+ });
+}
+
+module.exports = saveIndex;
diff --git a/save-static-file.js b/save-static-file.js
new file mode 100644
index 0000000..51791b4
--- /dev/null
+++ b/save-static-file.js
@@ -0,0 +1,22 @@
+const ncp = require("ncp");
+const path = require("path");
+const mkdirp = require("mkdirp");
+
+function saveStaticFile(filePath) {
+ return new Promise((resolve, reject) => {
+ const relativePath = path.relative("public", filePath);
+ const destination = path.join("output", relativePath);
+
+ mkdirp(path.dirname(destination), err => {
+ if (err) { return reject(err); }
+
+ ncp(filePath, destination, err => {
+ if (err) { return reject(err); }
+
+ resolve("ok");
+ });
+ });
+ });
+}
+
+module.exports = saveStaticFile;
diff --git a/templates/article.ejs b/templates/article.ejs
new file mode 100644
index 0000000..df12d51
--- /dev/null
+++ b/templates/article.ejs
@@ -0,0 +1,39 @@
+<article class="article">
+ <header class="title">
+ <% if (!timeless && hidden) { %>
+ <p>
+ <strong>Note:</strong>
+ This article is hidden and is only available to those with a link.
+ </p>
+ <% } %>
+
+ <% if (!timeless && date) { %>
+ <div class="date">
+ <%= date %>
+ </div>
+ <% } %>
+ <h1><%= title %></h1>
+
+ <% if (tags.length) { %>
+ <div>
+ <span class="posted-in">Posted in: </span>
+ <span>
+ <% tags.forEach(function(tag, i) { %>
+ <a href="/tags/<%= tag %>"><%= tag %></a><% if (i !== tags.length - 1) { %>, <% } %>
+ <% }) %>
+ </span>
+ </div>
+ <% } %>
+ </header>
+
+ <section class="content">
+ <%- body %>
+ </section>
+
+ <footer class="more details">
+ --
+ <br><br>
+ <!-- <a target="_blank" rel="noopener" href="<%= tweetUrl %>" class="tweet">tweet this</a> -->
+ <a href="/">&laquo; back to home</a>
+ </footer>
+</article>
diff --git a/templates/index.ejs b/templates/index.ejs
new file mode 100644
index 0000000..0bbbfdf
--- /dev/null
+++ b/templates/index.ejs
@@ -0,0 +1,21 @@
+<% if (typeof banner !== "undefined") { %>
+ <div class="banner"><%= banner %></div>
+<% } %>
+
+<% articles.forEach(function (article) { %>
+ <div class="article">
+ <div class="title">
+ <h1><a href="<%= article.route %>"><%= article.title %></a></h1>
+ </div>
+
+ <div class="content">
+ <p class="listing">
+ <%- (article.description || article.summary).replace(/<.?p>/g, "") %>
+ <a href="<%= article.route %>">
+ <span aria-hidden="true">read on &raquo;</span>
+ <span class="sr-only">continue reading this article</span>
+ </a>
+ </p>
+ </div>
+ </div>
+<% }) %>
diff --git a/templates/layout.ejs b/templates/layout.ejs
new file mode 100644
index 0000000..484c56e
--- /dev/null
+++ b/templates/layout.ejs
@@ -0,0 +1,122 @@
+<!doctype html>
+<html lang="en">
+
+<head>
+ <title>
+ <%= title %>
+ </title>
+
+ <link rel="stylesheet" href="/css/style.css">
+ <link rel="stylesheet" href="/css/tomorrow.min.css">
+ <link rel="stylesheet" href="/css/demos/clicking.css">
+ <link rel="stylesheet" href="/css/demos/hover.css">
+ <link rel="stylesheet" href="/css/demos/shared.css">
+ <link rel="stylesheet" href="/css/katex.min.css">
+
+ <% if (typeof isEditor !== "undefined" && isEditor) { %>
+ <link rel="stylesheet" href="/editor/editor.css">
+ <% } %>
+
+ <link href="https://fonts.googleapis.com/css?family=Inconsolata:400,700&font-display=swap" rel="stylesheet" type="text/css">
+ <meta charset="utf-8">
+ <meta name="viewport" content="width=device-width, initial-scale=1">
+ <link rel="Shortcut Icon" type="image/x-icon"
+ href="https://i.pinimg.com/originals/de/3a/63/de3a63c2afc0d9ccf7499607452d52a5.jpg" />
+
+ <meta property="og:title" content="<%= title %>" />
+ <meta name="Description" content="<%= description %>" />
+ <meta property="og:description" content="<%= description %>" />
+ <meta property="og:image" content="https://i.pinimg.com/originals/de/3a/63/de3a63c2afc0d9ccf7499607452d52a5.jpg" />
+
+ <meta name="twitter:card" content="summary" />
+ <meta name="twitter:site" content="@xFuwn" />
+ <meta name="twitter:title" content="<%= title %>" />
+ <meta name="twitter:description" content="<%= description %>" />
+ <meta name="twitter:image" content="https://i.pinimg.com/originals/de/3a/63/de3a63c2afc0d9ccf7499607452d52a5.jpg" />
+</head>
+
+<body>
+ <div class="main">
+ <header>
+ <span aria-hidden="true">//</span>
+ <strong>fuwn, a naritive through my struggles</strong>
+ <span class="sep" aria-hidden="true">.</span>
+ <a href="/">home</a>
+ <span class="sep" aria-hidden="true">.</span>
+ <a href="https://fuwn.me">more</a>
+ </header>
+
+ <%- body %>
+
+ <% if (typeof isEditor !== "undefined" && isEditor) { %>
+ <form id="editor">
+ <input type="hidden" name="description" id="editor-description" />
+ <table cellspacing="12">
+ <tr>
+ <td colspan="2">
+ <label>Load post</label>
+ <select id="editor-loader">
+ <option value="">Select an article</option>
+ <% articles.reverse().forEach((article) => { %>
+ <option value='<%= JSON.stringify(article) %>'>
+ <%= article.filename.replace(/^articles\//, "") %>
+ </option>
+ <% }) %>
+ </select>
+ </td>
+ </tr>
+ <tr>
+ <td>
+ <label>Title</label>
+ <input type="text" name="title" id="editor-title" value="New post" />
+ </td>
+
+ <td>
+ <label>Date</label>
+ <input type="text" name="date" id="editor-date" value="1970-01-01" />
+ </td>
+ </tr>
+ <tr>
+ <td>
+ <label>Route</label>
+ <input type="text" name="route" id="editor-route" value="/hello-world" />
+ </td>
+ <td>
+ <label>Filename</label>
+ <input type="text" name="filename" id="editor-filename" value="yyyy-mm-dd-hello-world.md" />
+ </td>
+ </tr>
+ <tr>
+ <td colspan="2">
+ <label>Content</label>
+ <br />
+ <textarea name="content" id="editor-content">Start writing...</textarea>
+ </td>
+ </tr>
+ <tr>
+ <td colspan="2">
+ <button id="editor-save-button">Save</button>
+ </td>
+ </tr>
+ </table>
+ </form>
+
+ <link rel="stylesheet" href="http://cdn.jsdelivr.net/npm/[email protected]/dist/katex.min.css" integrity="sha384-yFRtMMDnQtDRO8rLpMIKrtPCD5jdktao2TV19YiZYWMDkUR5GQZR/NOVTdquEx1j" crossorigin="anonymous">
+ <script defer src="http://cdn.jsdelivr.net/npm/[email protected]/dist/katex.min.js" integrity="sha384-9Nhn55MVVN0/4OFx7EE5kpFBPsEMZxKTCnA+4fqDmg12eCTqGi6+BB2LjY8brQxJ" crossorigin="anonymous"></script>
+ <script defer src="http://cdn.jsdelivr.net/npm/[email protected]/dist/contrib/auto-render.min.js" integrity="sha384-kWPLUVMOks5AQFrykwIup5lo0m3iMkkHrD0uJ4H5cjeGihAutqP0yW0J6dpFiVkI" crossorigin="anonymous"></script>
+ <% } %>
+ </div>
+
+ <script async src="/js/flasher.js"></script>
+ <script async src="/js/demos/clicking.js"></script>
+ <script async src="/js/demos/hover.js"></script>
+
+ <% if (typeof isEditor !== "undefined" && isEditor) { %>
+ <script src="/editor/ejs.min.js"></script>
+ <script src="/editor/strftime.min.js"></script>
+ <script src="/editor/marked.min.js"></script>
+ <script src="/editor/editor.js"></script>
+ <% } %>
+</body>
+
+</html>
diff --git a/yarn.lock b/yarn.lock
new file mode 100644
index 0000000..666a545
--- /dev/null
+++ b/yarn.lock
@@ -0,0 +1,2140 @@
+# THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY.
+# yarn lockfile v1
+
+
+ version "2.6.0"
+ resolved "https://registry.yarnpkg.com/@zeit/schemas/-/schemas-2.6.0.tgz#004e8e553b4cd53d538bd38eac7bcbf58a867fe3"
+ integrity sha512-uUrgZ8AxS+Lio0fZKAipJjAh415JyrOZowliZAzmnJSsf7piVL5w+G0+gFJ0KSu3QRhvui/7zuvpLz03YjXAhg==
+
+abbrev@1:
+ version "1.1.1"
+ resolved "https://registry.yarnpkg.com/abbrev/-/abbrev-1.1.1.tgz#f8f2c887ad10bf67f634f005b6987fed3179aac8"
+ integrity sha512-nne9/IiQ/hzIhY6pdDnbBtz7DjPTKrY00P/zvPSm5pOFkl6xuGrGnXn/VtTNNfNtAfZ9/1RtehkszU9qcTii0Q==
+
+accepts@~1.3.5, accepts@~1.3.7:
+ version "1.3.7"
+ resolved "https://registry.yarnpkg.com/accepts/-/accepts-1.3.7.tgz#531bc726517a3b2b41f850021c6cc15eaab507cd"
+ integrity sha512-Il80Qs2WjYlJIBNzNkK6KYqlVMTbZLXgHx2oT0pU/fjRHyEp+PEfEPY0R3WCwAGVOtauxh1hOxNgIf5bv7dQpA==
+ dependencies:
+ mime-types "~2.1.24"
+ negotiator "0.6.2"
+
+ version "6.5.3"
+ resolved "https://registry.yarnpkg.com/ajv/-/ajv-6.5.3.tgz#71a569d189ecf4f4f321224fecb166f071dd90f9"
+ integrity sha512-LqZ9wY+fx3UMiiPd741yB2pj3hhil+hQc8taf4o2QGRFpWgZ2V5C8HA165DY9sS3fJwsk7uT7ZlFEyC3Ig3lLg==
+ dependencies:
+ fast-deep-equal "^2.0.1"
+ fast-json-stable-stringify "^2.0.0"
+ json-schema-traverse "^0.4.1"
+ uri-js "^4.2.2"
+
+ansi-align@^2.0.0:
+ version "2.0.0"
+ resolved "https://registry.yarnpkg.com/ansi-align/-/ansi-align-2.0.0.tgz#c36aeccba563b89ceb556f3690f0b1d9e3547f7f"
+ integrity sha1-w2rsy6VjuJzrVW82kPCx2eNUf38=
+ dependencies:
+ string-width "^2.0.0"
+
+ansi-regex@^3.0.0:
+ version "3.0.0"
+ resolved "https://registry.yarnpkg.com/ansi-regex/-/ansi-regex-3.0.0.tgz#ed0317c322064f79466c02966bddb605ab37d998"
+ integrity sha1-7QMXwyIGT3lGbAKWa922Bas32Zg=
+
+ansi-styles@^3.2.1:
+ version "3.2.1"
+ resolved "https://registry.yarnpkg.com/ansi-styles/-/ansi-styles-3.2.1.tgz#41fbb20243e50b12be0f04b8dedbf07520ce841d"
+ integrity sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==
+ dependencies:
+ color-convert "^1.9.0"
+
+anymatch@^2.0.0:
+ version "2.0.0"
+ resolved "https://registry.yarnpkg.com/anymatch/-/anymatch-2.0.0.tgz#bcb24b4f37934d9aa7ac17b4adaf89e7c76ef2eb"
+ integrity sha512-5teOsQWABXHHBFP9y3skS5P3d/WfWXpv3FUpy+LorMrNYaT9pI4oLMQX7jzQ2KklNpGpWHzdCXTDT2Y3XGlZBw==
+ dependencies:
+ micromatch "^3.1.4"
+ normalize-path "^2.1.1"
+
+append-field@^1.0.0:
+ version "1.0.0"
+ resolved "https://registry.yarnpkg.com/append-field/-/append-field-1.0.0.tgz#1e3440e915f0b1203d23748e78edd7b9b5b43e56"
+ integrity sha1-HjRA6RXwsSA9I3SOeO3XubW0PlY=
+
+arch@^2.1.0:
+ version "2.2.0"
+ resolved "https://registry.yarnpkg.com/arch/-/arch-2.2.0.tgz#1bc47818f305764f23ab3306b0bfc086c5a29d11"
+ integrity sha512-Of/R0wqp83cgHozfIYLbBMnej79U/SVGOOyuB3VVFv1NRM/PSFMK12x9KVtiYzJqmnU5WR2qp0Z5rHb7sWGnFQ==
+
+ version "2.0.0"
+ resolved "https://registry.yarnpkg.com/arg/-/arg-2.0.0.tgz#c06e7ff69ab05b3a4a03ebe0407fac4cba657545"
+ integrity sha512-XxNTUzKnz1ctK3ZIcI2XUPlD96wbHP2nGqkPKpvk/HNRlPveYrXIVSTk9m3LcqOgDPg3B1nMvdV/K8wZd7PG4w==
+
+argparse@^1.0.7:
+ version "1.0.10"
+ resolved "https://registry.yarnpkg.com/argparse/-/argparse-1.0.10.tgz#bcd6791ea5ae09725e17e5ad988134cd40b3d911"
+ integrity sha512-o5Roy6tNG4SL/FOkCAN6RzjiakZS25RLYFrcMttJqbdd8BWrnA+fGz57iN5Pb06pvBGvl5gQ0B48dJlslXvoTg==
+ dependencies:
+ sprintf-js "~1.0.2"
+
+arr-diff@^4.0.0:
+ version "4.0.0"
+ resolved "https://registry.yarnpkg.com/arr-diff/-/arr-diff-4.0.0.tgz#d6461074febfec71e7e15235761a329a5dc7c520"
+ integrity sha1-1kYQdP6/7HHn4VI1dhoyml3HxSA=
+
+arr-flatten@^1.1.0:
+ version "1.1.0"
+ resolved "https://registry.yarnpkg.com/arr-flatten/-/arr-flatten-1.1.0.tgz#36048bbff4e7b47e136644316c99669ea5ae91f1"
+ integrity sha512-L3hKV5R/p5o81R7O02IGnwpDmkp6E982XhtbuwSe3O4qOtMMMtodicASA1Cny2U+aCXcNpml+m4dPsvsJ3jatg==
+
+arr-union@^3.1.0:
+ version "3.1.0"
+ resolved "https://registry.yarnpkg.com/arr-union/-/arr-union-3.1.0.tgz#e39b09aea9def866a8f206e288af63919bae39c4"
+ integrity sha1-45sJrqne+Gao8gbiiK9jkZuuOcQ=
+
+ version "1.1.1"
+ resolved "https://registry.yarnpkg.com/array-flatten/-/array-flatten-1.1.1.tgz#9a5f699051b1e7073328f2a008968b64ea2955d2"
+ integrity sha1-ml9pkFGx5wczKPKgCJaLZOopVdI=
+
+array-unique@^0.3.2:
+ version "0.3.2"
+ resolved "https://registry.yarnpkg.com/array-unique/-/array-unique-0.3.2.tgz#a894b75d4bc4f6cd679ef3244a9fd8f46ae2d428"
+ integrity sha1-qJS3XUvE9s1nnvMkSp/Y9Gri1Cg=
+
+assign-symbols@^1.0.0:
+ version "1.0.0"
+ resolved "https://registry.yarnpkg.com/assign-symbols/-/assign-symbols-1.0.0.tgz#59667f41fadd4f20ccbc2bb96b8d4f7f78ec0367"
+ integrity sha1-WWZ/QfrdTyDMvCu5a41Pf3jsA2c=
+
+async-each@^1.0.1:
+ version "1.0.3"
+ resolved "https://registry.yarnpkg.com/async-each/-/async-each-1.0.3.tgz#b727dbf87d7651602f06f4d4ac387f47d91b0cbf"
+ integrity sha512-z/WhQ5FPySLdvREByI2vZiTWwCnF0moMJ1hK9YQwDTHKh6I7/uSckMetoRGb5UBZPC1z0jlw+n/XCgjeH7y1AQ==
+
+atob@^2.1.2:
+ version "2.1.2"
+ resolved "https://registry.yarnpkg.com/atob/-/atob-2.1.2.tgz#6d9517eb9e030d2436666651e86bd9f6f13533c9"
+ integrity sha512-Wm6ukoaOGJi/73p/cl2GvLjTI5JM1k/O14isD73YML8StrH/7/lRFgmg8nICZgD3bZZvjwCGxtMOD3wWNAu8cg==
+
+balanced-match@^1.0.0:
+ version "1.0.0"
+ resolved "https://registry.yarnpkg.com/balanced-match/-/balanced-match-1.0.0.tgz#89b4d199ab2bee49de164ea02b89ce462d71b767"
+ integrity sha1-ibTRmasr7kneFk6gK4nORi1xt2c=
+
+base@^0.11.1:
+ version "0.11.2"
+ resolved "https://registry.yarnpkg.com/base/-/base-0.11.2.tgz#7bde5ced145b6d551a90db87f83c558b4eb48a8f"
+ integrity sha512-5T6P4xPgpp0YDFvSWwEZ4NoE3aM4QBQXDzmVbraCkFj8zHM+mba8SyqB5DbZWyR7mYHo6Y7BdQo3MoA4m0TeQg==
+ dependencies:
+ cache-base "^1.0.1"
+ class-utils "^0.3.5"
+ component-emitter "^1.2.1"
+ define-property "^1.0.0"
+ isobject "^3.0.1"
+ mixin-deep "^1.2.0"
+ pascalcase "^0.1.1"
+
+binary-extensions@^1.0.0:
+ version "1.13.1"
+ resolved "https://registry.yarnpkg.com/binary-extensions/-/binary-extensions-1.13.1.tgz#598afe54755b2868a5330d2aff9d4ebb53209b65"
+ integrity sha512-Un7MIEDdUC5gNpcGDV97op1Ywk748MpHcFTHoYs6qnj1Z3j7I53VG3nwZhKzoBZmbdRNnb6WRdFlwl7tSDuZGw==
+
+bindings@^1.5.0:
+ version "1.5.0"
+ resolved "https://registry.yarnpkg.com/bindings/-/bindings-1.5.0.tgz#10353c9e945334bc0511a6d90b38fbc7c9c504df"
+ integrity sha512-p2q/t/mhvuOj/UeLlV6566GD/guowlr0hHxClI0W9m7MWYkL1F0hLo+0Aexs9HSPCtR1SXQ0TD3MMKrXZajbiQ==
+ dependencies:
+ file-uri-to-path "1.0.0"
+
+ version "1.19.0"
+ resolved "https://registry.yarnpkg.com/body-parser/-/body-parser-1.19.0.tgz#96b2709e57c9c4e09a6fd66a8fd979844f69f08a"
+ integrity sha512-dhEPs72UPbDnAQJ9ZKMNTP6ptJaionhP5cBb541nXPlW60Jepo9RV/a4fX4XWW9CuFNK22krhrj1+rgzifNCsw==
+ dependencies:
+ bytes "3.1.0"
+ content-type "~1.0.4"
+ debug "2.6.9"
+ depd "~1.1.2"
+ http-errors "1.7.2"
+ iconv-lite "0.4.24"
+ on-finished "~2.3.0"
+ qs "6.7.0"
+ raw-body "2.4.0"
+ type-is "~1.6.17"
+
[email protected], boxen@^1.2.1:
+ version "1.3.0"
+ resolved "https://registry.yarnpkg.com/boxen/-/boxen-1.3.0.tgz#55c6c39a8ba58d9c61ad22cd877532deb665a20b"
+ integrity sha512-TNPjfTr432qx7yOjQyaXm3dSR0MH9vXp7eT1BFSl/C51g+EFnOR9hTg1IreahGBmDNCehscshe45f+C1TBZbLw==
+ dependencies:
+ ansi-align "^2.0.0"
+ camelcase "^4.0.0"
+ chalk "^2.0.1"
+ cli-boxes "^1.0.0"
+ string-width "^2.0.0"
+ term-size "^1.2.0"
+ widest-line "^2.0.0"
+
+brace-expansion@^1.1.7:
+ version "1.1.11"
+ resolved "https://registry.yarnpkg.com/brace-expansion/-/brace-expansion-1.1.11.tgz#3c7fcbf529d87226f3d2f52b966ff5271eb441dd"
+ integrity sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==
+ dependencies:
+ balanced-match "^1.0.0"
+ concat-map "0.0.1"
+
+braces@^2.3.1, braces@^2.3.2:
+ version "2.3.2"
+ resolved "https://registry.yarnpkg.com/braces/-/braces-2.3.2.tgz#5979fd3f14cd531565e5fa2df1abfff1dfaee729"
+ integrity sha512-aNdbnj9P8PjdXU4ybaWLK2IF3jc/EoDYbC7AazW6to3TRsfXxscC9UXOB5iDiEQrkyIbWp2SLQda4+QAa7nc3w==
+ dependencies:
+ arr-flatten "^1.1.0"
+ array-unique "^0.3.2"
+ extend-shallow "^2.0.1"
+ fill-range "^4.0.0"
+ isobject "^3.0.1"
+ repeat-element "^1.1.2"
+ snapdragon "^0.8.1"
+ snapdragon-node "^2.0.1"
+ split-string "^3.0.2"
+ to-regex "^3.0.1"
+
+buffer-from@^1.0.0:
+ version "1.1.1"
+ resolved "https://registry.yarnpkg.com/buffer-from/-/buffer-from-1.1.1.tgz#32713bc028f75c02fdb710d7c7bcec1f2c6070ef"
+ integrity sha512-MQcXEUbCKtEo7bhqEs6560Hyd4XaovZlO/k9V3hjVUF/zwW7KBVdSK4gIt/bzwS9MbR5qob+F5jusZsb0YQK2A==
+
+busboy@^0.2.11:
+ version "0.2.14"
+ resolved "https://registry.yarnpkg.com/busboy/-/busboy-0.2.14.tgz#6c2a622efcf47c57bbbe1e2a9c37ad36c7925453"
+ integrity sha1-bCpiLvz0fFe7vh4qnDetNseSVFM=
+ dependencies:
+ dicer "0.2.5"
+ readable-stream "1.1.x"
+
+ version "3.0.0"
+ resolved "https://registry.yarnpkg.com/bytes/-/bytes-3.0.0.tgz#d32815404d689699f85a4ea4fa8755dd13a96048"
+ integrity sha1-0ygVQE1olpn4Wk6k+odV3ROpYEg=
+
+ version "3.1.0"
+ resolved "https://registry.yarnpkg.com/bytes/-/bytes-3.1.0.tgz#f6cf7933a360e0588fa9fde85651cdc7f805d1f6"
+ integrity sha512-zauLjrfCG+xvoyaqLoV8bLVXXNGC4JqlxFCutSDWA6fJrTo2ZuvLYTqZ7aHBLZSMOopbzwv8f+wZcVzfVTI2Dg==
+
+cache-base@^1.0.1:
+ version "1.0.1"
+ resolved "https://registry.yarnpkg.com/cache-base/-/cache-base-1.0.1.tgz#0a7f46416831c8b662ee36fe4e7c59d76f666ab2"
+ integrity sha512-AKcdTnFSWATd5/GCPRxr2ChwIJ85CeyrEyjRHlKxQ56d4XJMGym0uAiKn0xbLOGOl3+yRpOTi484dVCEc5AUzQ==
+ dependencies:
+ collection-visit "^1.0.0"
+ component-emitter "^1.2.1"
+ get-value "^2.0.6"
+ has-value "^1.0.0"
+ isobject "^3.0.1"
+ set-value "^2.0.0"
+ to-object-path "^0.3.0"
+ union-value "^1.0.0"
+ unset-value "^1.0.0"
+
+camelcase@^4.0.0:
+ version "4.1.0"
+ resolved "https://registry.yarnpkg.com/camelcase/-/camelcase-4.1.0.tgz#d545635be1e33c542649c69173e5de6acfae34dd"
+ integrity sha1-1UVjW+HjPFQmScaRc+Xeas+uNN0=
+
+capture-stack-trace@^1.0.0:
+ version "1.0.1"
+ resolved "https://registry.yarnpkg.com/capture-stack-trace/-/capture-stack-trace-1.0.1.tgz#a6c0bbe1f38f3aa0b92238ecb6ff42c344d4135d"
+ integrity sha512-mYQLZnx5Qt1JgB1WEiMCf2647plpGeQ2NMR/5L0HNZzGQo4fuSPnK+wjfPnKZV0aiJDgzmWqqkV/g7JD+DW0qw==
+
+ version "2.4.1"
+ resolved "https://registry.yarnpkg.com/chalk/-/chalk-2.4.1.tgz#18c49ab16a037b6eb0152cc83e3471338215b66e"
+ integrity sha512-ObN6h1v2fTJSmUXoS3nMQ92LbDK9be4TV+6G+omQlGJFdcUX5heKi1LZ1YnRMIgwTLEj3E24bT6tYni50rlCfQ==
+ dependencies:
+ ansi-styles "^3.2.1"
+ escape-string-regexp "^1.0.5"
+ supports-color "^5.3.0"
+
+chalk@^2.0.1:
+ version "2.4.2"
+ resolved "https://registry.yarnpkg.com/chalk/-/chalk-2.4.2.tgz#cd42541677a54333cf541a49108c1432b44c9424"
+ integrity sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==
+ dependencies:
+ ansi-styles "^3.2.1"
+ escape-string-regexp "^1.0.5"
+ supports-color "^5.3.0"
+
+chokidar@^2.1.8:
+ version "2.1.8"
+ resolved "https://registry.yarnpkg.com/chokidar/-/chokidar-2.1.8.tgz#804b3a7b6a99358c3c5c61e71d8728f041cff917"
+ integrity sha512-ZmZUazfOzf0Nve7duiCKD23PFSCs4JPoYyccjUFF3aQkQadqBhfzhjkwBH2mNOG9cTBwhamM37EIsIkZw3nRgg==
+ dependencies:
+ anymatch "^2.0.0"
+ async-each "^1.0.1"
+ braces "^2.3.2"
+ glob-parent "^3.1.0"
+ inherits "^2.0.3"
+ is-binary-path "^1.0.0"
+ is-glob "^4.0.0"
+ normalize-path "^3.0.0"
+ path-is-absolute "^1.0.0"
+ readdirp "^2.2.1"
+ upath "^1.1.1"
+ optionalDependencies:
+ fsevents "^1.2.7"
+
+ci-info@^1.5.0:
+ version "1.6.0"
+ resolved "https://registry.yarnpkg.com/ci-info/-/ci-info-1.6.0.tgz#2ca20dbb9ceb32d4524a683303313f0304b1e497"
+ integrity sha512-vsGdkwSCDpWmP80ncATX7iea5DWQemg1UgCW5J8tqjU3lYw4FBYuj89J0CTVomA7BEfvSZd84GmHko+MxFQU2A==
+
+class-utils@^0.3.5:
+ version "0.3.6"
+ resolved "https://registry.yarnpkg.com/class-utils/-/class-utils-0.3.6.tgz#f93369ae8b9a7ce02fd41faad0ca83033190c463"
+ integrity sha512-qOhPa/Fj7s6TY8H8esGu5QNpMMQxz79h+urzrNYN6mn+9BnxlDGf5QZ+XeCDsxSjPqsSR56XOZOJmpeurnLMeg==
+ dependencies:
+ arr-union "^3.1.0"
+ define-property "^0.2.5"
+ isobject "^3.0.0"
+ static-extend "^0.1.1"
+
+cli-boxes@^1.0.0:
+ version "1.0.0"
+ resolved "https://registry.yarnpkg.com/cli-boxes/-/cli-boxes-1.0.0.tgz#4fa917c3e59c94a004cd61f8ee509da651687143"
+ integrity sha1-T6kXw+WclKAEzWH47lCdplFocUM=
+
+ version "1.2.3"
+ resolved "https://registry.yarnpkg.com/clipboardy/-/clipboardy-1.2.3.tgz#0526361bf78724c1f20be248d428e365433c07ef"
+ integrity sha512-2WNImOvCRe6r63Gk9pShfkwXsVtKCroMAevIbiae021mS850UkWPbevxsBz3tnvjZIEGvlwaqCPsw+4ulzNgJA==
+ dependencies:
+ arch "^2.1.0"
+ execa "^0.8.0"
+
+collection-visit@^1.0.0:
+ version "1.0.0"
+ resolved "https://registry.yarnpkg.com/collection-visit/-/collection-visit-1.0.0.tgz#4bc0373c164bc3291b4d368c829cf1a80a59dca0"
+ integrity sha1-S8A3PBZLwykbTTaMgpzxqApZ3KA=
+ dependencies:
+ map-visit "^1.0.0"
+ object-visit "^1.0.0"
+
+color-convert@^1.9.0:
+ version "1.9.3"
+ resolved "https://registry.yarnpkg.com/color-convert/-/color-convert-1.9.3.tgz#bb71850690e1f136567de629d2d5471deda4c1e8"
+ integrity sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==
+ dependencies:
+ color-name "1.1.3"
+
+ version "1.1.3"
+ resolved "https://registry.yarnpkg.com/color-name/-/color-name-1.1.3.tgz#a7d0558bd89c42f795dd42328f740831ca53bc25"
+ integrity sha1-p9BVi9icQveV3UIyj3QIMcpTvCU=
+
+commander@^2.19.0:
+ version "2.20.3"
+ resolved "https://registry.yarnpkg.com/commander/-/commander-2.20.3.tgz#fd485e84c03eb4881c20722ba48035e8531aeb33"
+ integrity sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ==
+
+component-emitter@^1.2.1:
+ version "1.3.0"
+ resolved "https://registry.yarnpkg.com/component-emitter/-/component-emitter-1.3.0.tgz#16e4070fba8ae29b679f2215853ee181ab2eabc0"
+ integrity sha512-Rd3se6QB+sO1TwqZjscQrurpEPIfO0/yYnSin6Q/rD3mOutHvUrCAhJub3r90uNb+SESBuE0QYoB90YdfatsRg==
+
+compressible@~2.0.14:
+ version "2.0.18"
+ resolved "https://registry.yarnpkg.com/compressible/-/compressible-2.0.18.tgz#af53cca6b070d4c3c0750fbd77286a6d7cc46fba"
+ integrity sha512-AF3r7P5dWxL8MxyITRMlORQNaOA2IkAFaTr4k7BUumjPtRpGDTZpl0Pb1XCO6JeDCBdp126Cgs9sMxqSjgYyRg==
+ dependencies:
+ mime-db ">= 1.43.0 < 2"
+
+ version "1.7.3"
+ resolved "https://registry.yarnpkg.com/compression/-/compression-1.7.3.tgz#27e0e176aaf260f7f2c2813c3e440adb9f1993db"
+ integrity sha512-HSjyBG5N1Nnz7tF2+O7A9XUhyjru71/fwgNb7oIsEVHR0WShfs2tIS/EySLgiTe98aOK18YDlMXpzjCXY/n9mg==
+ dependencies:
+ accepts "~1.3.5"
+ bytes "3.0.0"
+ compressible "~2.0.14"
+ debug "2.6.9"
+ on-headers "~1.0.1"
+ safe-buffer "5.1.2"
+ vary "~1.1.2"
+
+ version "0.0.1"
+ resolved "https://registry.yarnpkg.com/concat-map/-/concat-map-0.0.1.tgz#d8a96bd77fd68df7793a73036a3ba0d5405d477b"
+ integrity sha1-2Klr13/Wjfd5OnMDajug1UBdR3s=
+
+concat-stream@^1.5.2:
+ version "1.6.2"
+ resolved "https://registry.yarnpkg.com/concat-stream/-/concat-stream-1.6.2.tgz#904bdf194cd3122fc675c77fc4ac3d4ff0fd1a34"
+ integrity sha512-27HBghJxjiZtIk3Ycvn/4kbJk/1uZuJFfuPEns6LaEvpvG1f0hTea8lilrouyo9mVc2GWdcEZ8OLoGmSADlrCw==
+ dependencies:
+ buffer-from "^1.0.0"
+ inherits "^2.0.3"
+ readable-stream "^2.2.2"
+ typedarray "^0.0.6"
+
+configstore@^3.0.0:
+ version "3.1.5"
+ resolved "https://registry.yarnpkg.com/configstore/-/configstore-3.1.5.tgz#e9af331fadc14dabd544d3e7e76dc446a09a530f"
+ integrity sha512-nlOhI4+fdzoK5xmJ+NY+1gZK56bwEaWZr8fYuXohZ9Vkc1o3a4T/R3M+yE/w7x/ZVJ1zF8c+oaOvF0dztdUgmA==
+ dependencies:
+ dot-prop "^4.2.1"
+ graceful-fs "^4.1.2"
+ make-dir "^1.0.0"
+ unique-string "^1.0.0"
+ write-file-atomic "^2.0.0"
+ xdg-basedir "^3.0.0"
+
+ version "0.5.2"
+ resolved "https://registry.yarnpkg.com/content-disposition/-/content-disposition-0.5.2.tgz#0cf68bb9ddf5f2be7961c3a85178cb85dba78cb4"
+ integrity sha1-DPaLud318r55YcOoUXjLhdunjLQ=
+
+ version "0.5.3"
+ resolved "https://registry.yarnpkg.com/content-disposition/-/content-disposition-0.5.3.tgz#e130caf7e7279087c5616c2007d0485698984fbd"
+ integrity sha512-ExO0774ikEObIAEV9kDo50o+79VCUdEB6n6lzKgGwupcVeRlhrj3qGAfwq8G6uBJjkqLrhT0qEYFcWng8z1z0g==
+ dependencies:
+ safe-buffer "5.1.2"
+
+content-type@~1.0.4:
+ version "1.0.4"
+ resolved "https://registry.yarnpkg.com/content-type/-/content-type-1.0.4.tgz#e138cc75e040c727b1966fe5e5f8c9aee256fe3b"
+ integrity sha512-hIP3EEPs8tB9AT1L+NUqtwOAps4mk2Zob89MWXMHjHWg9milF/j4osnnQLXBCBFBk/tvIG/tUc9mOUJiPBhPXA==
+
+ version "1.0.6"
+ resolved "https://registry.yarnpkg.com/cookie-signature/-/cookie-signature-1.0.6.tgz#e303a882b342cc3ee8ca513a79999734dab3ae2c"
+ integrity sha1-4wOogrNCzD7oylE6eZmXNNqzriw=
+
+ version "0.4.0"
+ resolved "https://registry.yarnpkg.com/cookie/-/cookie-0.4.0.tgz#beb437e7022b3b6d49019d088665303ebe9c14ba"
+ integrity sha512-+Hp8fLp57wnUSt0tY0tHEXh4voZRDnoIrZPqlo3DPiI4y9lwg/jqx+1Om94/W6ZaPDOUbnjOt/99w66zk+l1Xg==
+
+copy-descriptor@^0.1.0:
+ version "0.1.1"
+ resolved "https://registry.yarnpkg.com/copy-descriptor/-/copy-descriptor-0.1.1.tgz#676f6eb3c39997c2ee1ac3a924fd6124748f578d"
+ integrity sha1-Z29us8OZl8LuGsOpJP1hJHSPV40=
+
+core-util-is@~1.0.0:
+ version "1.0.2"
+ resolved "https://registry.yarnpkg.com/core-util-is/-/core-util-is-1.0.2.tgz#b5fd54220aa2bc5ab57aab7140c940754503c1a7"
+ integrity sha1-tf1UIgqivFq1eqtxQMlAdUUDwac=
+
+create-error-class@^3.0.0:
+ version "3.0.2"
+ resolved "https://registry.yarnpkg.com/create-error-class/-/create-error-class-3.0.2.tgz#06be7abef947a3f14a30fd610671d401bca8b7b6"
+ integrity sha1-Br56vvlHo/FKMP1hBnHUAbyot7Y=
+ dependencies:
+ capture-stack-trace "^1.0.0"
+
+cross-spawn@^5.0.1:
+ version "5.1.0"
+ resolved "https://registry.yarnpkg.com/cross-spawn/-/cross-spawn-5.1.0.tgz#e8bd0efee58fcff6f8f94510a0a554bbfa235449"
+ integrity sha1-6L0O/uWPz/b4+UUQoKVUu/ojVEk=
+ dependencies:
+ lru-cache "^4.0.1"
+ shebang-command "^1.2.0"
+ which "^1.2.9"
+
+crypto-random-string@^1.0.0:
+ version "1.0.0"
+ resolved "https://registry.yarnpkg.com/crypto-random-string/-/crypto-random-string-1.0.0.tgz#a230f64f568310e1498009940790ec99545bca7e"
+ integrity sha1-ojD2T1aDEOFJgAmUB5DsmVRbyn4=
+
[email protected], debug@^2.2.0, debug@^2.3.3:
+ version "2.6.9"
+ resolved "https://registry.yarnpkg.com/debug/-/debug-2.6.9.tgz#5d128515df134ff327e90a4c93f4e077a536341f"
+ integrity sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==
+ dependencies:
+ ms "2.0.0"
+
+debug@^3.2.6:
+ version "3.2.6"
+ resolved "https://registry.yarnpkg.com/debug/-/debug-3.2.6.tgz#e83d17de16d8a7efb7717edbe5fb10135eee629b"
+ integrity sha512-mel+jf7nrtEl5Pn1Qx46zARXKDpBbvzezse7p7LqINmdoIk8PYP5SySaxEmYv6TZ0JyEKA1hsCId6DIhgITtWQ==
+ dependencies:
+ ms "^2.1.1"
+
+decode-uri-component@^0.2.0:
+ version "0.2.0"
+ resolved "https://registry.yarnpkg.com/decode-uri-component/-/decode-uri-component-0.2.0.tgz#eb3913333458775cb84cd1a1fae062106bb87545"
+ integrity sha1-6zkTMzRYd1y4TNGh+uBiEGu4dUU=
+
+deep-extend@^0.6.0:
+ version "0.6.0"
+ resolved "https://registry.yarnpkg.com/deep-extend/-/deep-extend-0.6.0.tgz#c4fa7c95404a17a9c3e8ca7e1537312b736330ac"
+ integrity sha512-LOHxIOaPYdHlJRtCQfDIVZtfw/ufM8+rVj649RIHzcm/vGwQRXFt6OPqIFWsm2XEMrNIEtWR64sY1LEKD2vAOA==
+
+define-property@^0.2.5:
+ version "0.2.5"
+ resolved "https://registry.yarnpkg.com/define-property/-/define-property-0.2.5.tgz#c35b1ef918ec3c990f9a5bc57be04aacec5c8116"
+ integrity sha1-w1se+RjsPJkPmlvFe+BKrOxcgRY=
+ dependencies:
+ is-descriptor "^0.1.0"
+
+define-property@^1.0.0:
+ version "1.0.0"
+ resolved "https://registry.yarnpkg.com/define-property/-/define-property-1.0.0.tgz#769ebaaf3f4a63aad3af9e8d304c9bbe79bfb0e6"
+ integrity sha1-dp66rz9KY6rTr56NMEybvnm/sOY=
+ dependencies:
+ is-descriptor "^1.0.0"
+
+define-property@^2.0.2:
+ version "2.0.2"
+ resolved "https://registry.yarnpkg.com/define-property/-/define-property-2.0.2.tgz#d459689e8d654ba77e02a817f8710d702cb16e9d"
+ integrity sha512-jwK2UV4cnPpbcG7+VRARKTZPUWowwXA8bzH5NP6ud0oeAxyYPuGZUAC7hMugpCdz4BeSZl2Dl9k66CHJ/46ZYQ==
+ dependencies:
+ is-descriptor "^1.0.2"
+ isobject "^3.0.1"
+
+depd@~1.1.2:
+ version "1.1.2"
+ resolved "https://registry.yarnpkg.com/depd/-/depd-1.1.2.tgz#9bcd52e14c097763e749b274c4346ed2e560b5a9"
+ integrity sha1-m81S4UwJd2PnSbJ0xDRu0uVgtak=
+
+destroy@~1.0.4:
+ version "1.0.4"
+ resolved "https://registry.yarnpkg.com/destroy/-/destroy-1.0.4.tgz#978857442c44749e4206613e37946205826abd80"
+ integrity sha1-l4hXRCxEdJ5CBmE+N5RiBYJqvYA=
+
+ version "0.2.5"
+ resolved "https://registry.yarnpkg.com/dicer/-/dicer-0.2.5.tgz#5996c086bb33218c812c090bddc09cd12facb70f"
+ integrity sha1-WZbAhrszIYyBLAkL3cCc0S+stw8=
+ dependencies:
+ readable-stream "1.1.x"
+ streamsearch "0.1.2"
+
+dot-prop@^4.2.1:
+ version "4.2.1"
+ resolved "https://registry.yarnpkg.com/dot-prop/-/dot-prop-4.2.1.tgz#45884194a71fc2cda71cbb4bceb3a4dd2f433ba4"
+ integrity sha512-l0p4+mIuJIua0mhxGoh4a+iNL9bmeK5DvnSVQa6T0OhrVmaEa1XScX5Etc673FePCJOArq/4Pa2cLGODUWTPOQ==
+ dependencies:
+ is-obj "^1.0.0"
+
+duplexer3@^0.1.4:
+ version "0.1.4"
+ resolved "https://registry.yarnpkg.com/duplexer3/-/duplexer3-0.1.4.tgz#ee01dd1cac0ed3cbc7fdbea37dc0a8f1ce002ce2"
+ integrity sha1-7gHdHKwO08vH/b6jfcCo8c4ALOI=
+
+ version "1.1.1"
+ resolved "https://registry.yarnpkg.com/ee-first/-/ee-first-1.1.1.tgz#590c61156b0ae2f4f0255732a158b266bc56b21d"
+ integrity sha1-WQxhFWsK4vTwJVcyoViyZrxWsh0=
+
+ejs@^2.4.1:
+ version "2.7.4"
+ resolved "https://registry.yarnpkg.com/ejs/-/ejs-2.7.4.tgz#48661287573dcc53e366c7a1ae52c3a120eec9ba"
+ integrity sha512-7vmuyh5+kuUyJKePhQfRQBhXV5Ce+RnaeeQArKu1EAMpL3WbgMt5WG6uQZpEVvYSSsxMXRKOewtDk9RaTKXRlA==
+
+encodeurl@~1.0.2:
+ version "1.0.2"
+ resolved "https://registry.yarnpkg.com/encodeurl/-/encodeurl-1.0.2.tgz#ad3ff4c86ec2d029322f5a02c3a9a606c95b3f59"
+ integrity sha1-rT/0yG7C0CkyL1oCw6mmBslbP1k=
+
+escape-html@~1.0.3:
+ version "1.0.3"
+ resolved "https://registry.yarnpkg.com/escape-html/-/escape-html-1.0.3.tgz#0258eae4d3d0c0974de1c169188ef0051d1d1988"
+ integrity sha1-Aljq5NPQwJdN4cFpGI7wBR0dGYg=
+
+escape-string-regexp@^1.0.5:
+ version "1.0.5"
+ resolved "https://registry.yarnpkg.com/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz#1b61c0562190a8dff6ae3bb2cf0200ca130b86d4"
+ integrity sha1-G2HAViGQqN/2rjuyzwIAyhMLhtQ=
+
+esprima@^4.0.0:
+ version "4.0.1"
+ resolved "https://registry.yarnpkg.com/esprima/-/esprima-4.0.1.tgz#13b04cdb3e6c5d19df91ab6987a8695619b0aa71"
+ integrity sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A==
+
+etag@~1.8.1:
+ version "1.8.1"
+ resolved "https://registry.yarnpkg.com/etag/-/etag-1.8.1.tgz#41ae2eeb65efa62268aebfea83ac7d79299b0887"
+ integrity sha1-Qa4u62XvpiJorr/qg6x9eSmbCIc=
+
+execa@^0.7.0:
+ version "0.7.0"
+ resolved "https://registry.yarnpkg.com/execa/-/execa-0.7.0.tgz#944becd34cc41ee32a63a9faf27ad5a65fc59777"
+ integrity sha1-lEvs00zEHuMqY6n68nrVpl/Fl3c=
+ dependencies:
+ cross-spawn "^5.0.1"
+ get-stream "^3.0.0"
+ is-stream "^1.1.0"
+ npm-run-path "^2.0.0"
+ p-finally "^1.0.0"
+ signal-exit "^3.0.0"
+ strip-eof "^1.0.0"
+
+execa@^0.8.0:
+ version "0.8.0"
+ resolved "https://registry.yarnpkg.com/execa/-/execa-0.8.0.tgz#d8d76bbc1b55217ed190fd6dd49d3c774ecfc8da"
+ integrity sha1-2NdrvBtVIX7RkP1t1J08d07PyNo=
+ dependencies:
+ cross-spawn "^5.0.1"
+ get-stream "^3.0.0"
+ is-stream "^1.1.0"
+ npm-run-path "^2.0.0"
+ p-finally "^1.0.0"
+ signal-exit "^3.0.0"
+ strip-eof "^1.0.0"
+
+expand-brackets@^2.1.4:
+ version "2.1.4"
+ resolved "https://registry.yarnpkg.com/expand-brackets/-/expand-brackets-2.1.4.tgz#b77735e315ce30f6b6eff0f83b04151a22449622"
+ integrity sha1-t3c14xXOMPa27/D4OwQVGiJEliI=
+ dependencies:
+ debug "^2.3.3"
+ define-property "^0.2.5"
+ extend-shallow "^2.0.1"
+ posix-character-classes "^0.1.0"
+ regex-not "^1.0.0"
+ snapdragon "^0.8.1"
+ to-regex "^3.0.1"
+
+express@^4.16.2:
+ version "4.17.1"
+ resolved "https://registry.yarnpkg.com/express/-/express-4.17.1.tgz#4491fc38605cf51f8629d39c2b5d026f98a4c134"
+ integrity sha512-mHJ9O79RqluphRrcw2X/GTh3k9tVv8YcoyY4Kkh4WDMUYKRZUq0h1o0w2rrrxBqM7VoeUVqgb27xlEMXTnYt4g==
+ dependencies:
+ accepts "~1.3.7"
+ array-flatten "1.1.1"
+ body-parser "1.19.0"
+ content-disposition "0.5.3"
+ content-type "~1.0.4"
+ cookie "0.4.0"
+ cookie-signature "1.0.6"
+ debug "2.6.9"
+ depd "~1.1.2"
+ encodeurl "~1.0.2"
+ escape-html "~1.0.3"
+ etag "~1.8.1"
+ finalhandler "~1.1.2"
+ fresh "0.5.2"
+ merge-descriptors "1.0.1"
+ methods "~1.1.2"
+ on-finished "~2.3.0"
+ parseurl "~1.3.3"
+ path-to-regexp "0.1.7"
+ proxy-addr "~2.0.5"
+ qs "6.7.0"
+ range-parser "~1.2.1"
+ safe-buffer "5.1.2"
+ send "0.17.1"
+ serve-static "1.14.1"
+ setprototypeof "1.1.1"
+ statuses "~1.5.0"
+ type-is "~1.6.18"
+ utils-merge "1.0.1"
+ vary "~1.1.2"
+
+extend-shallow@^2.0.1:
+ version "2.0.1"
+ resolved "https://registry.yarnpkg.com/extend-shallow/-/extend-shallow-2.0.1.tgz#51af7d614ad9a9f610ea1bafbb989d6b1c56890f"
+ integrity sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=
+ dependencies:
+ is-extendable "^0.1.0"
+
+extend-shallow@^3.0.0, extend-shallow@^3.0.2:
+ version "3.0.2"
+ resolved "https://registry.yarnpkg.com/extend-shallow/-/extend-shallow-3.0.2.tgz#26a71aaf073b39fb2127172746131c2704028db8"
+ integrity sha1-Jqcarwc7OfshJxcnRhMcJwQCjbg=
+ dependencies:
+ assign-symbols "^1.0.0"
+ is-extendable "^1.0.1"
+
+extglob@^2.0.4:
+ version "2.0.4"
+ resolved "https://registry.yarnpkg.com/extglob/-/extglob-2.0.4.tgz#ad00fe4dc612a9232e8718711dc5cb5ab0285543"
+ integrity sha512-Nmb6QXkELsuBr24CJSkilo6UHHgbekK5UiZgfE6UHD3Eb27YC6oD+bhcT+tJ6cl8dmsgdQxnWlcry8ksBIBLpw==
+ dependencies:
+ array-unique "^0.3.2"
+ define-property "^1.0.0"
+ expand-brackets "^2.1.4"
+ extend-shallow "^2.0.1"
+ fragment-cache "^0.2.1"
+ regex-not "^1.0.0"
+ snapdragon "^0.8.1"
+ to-regex "^3.0.1"
+
+fast-deep-equal@^2.0.1:
+ version "2.0.1"
+ resolved "https://registry.yarnpkg.com/fast-deep-equal/-/fast-deep-equal-2.0.1.tgz#7b05218ddf9667bf7f370bf7fdb2cb15fdd0aa49"
+ integrity sha1-ewUhjd+WZ79/Nwv3/bLLFf3Qqkk=
+
+fast-json-stable-stringify@^2.0.0:
+ version "2.1.0"
+ resolved "https://registry.yarnpkg.com/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz#874bf69c6f404c2b5d99c481341399fd55892633"
+ integrity sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==
+
+ version "1.1.3"
+ resolved "https://registry.yarnpkg.com/fast-url-parser/-/fast-url-parser-1.1.3.tgz#f4af3ea9f34d8a271cf58ad2b3759f431f0b318d"
+ integrity sha1-9K8+qfNNiicc9YrSs3WfQx8LMY0=
+ dependencies:
+ punycode "^1.3.2"
+
+ version "1.0.0"
+ resolved "https://registry.yarnpkg.com/file-uri-to-path/-/file-uri-to-path-1.0.0.tgz#553a7b8446ff6f684359c445f1e37a05dacc33dd"
+ integrity sha512-0Zt+s3L7Vf1biwWZ29aARiVYLx7iMGnEUl9x33fbB/j3jR81u/O2LbqK+Bm1CDSNDKVtJ/YjwY7TUd5SkeLQLw==
+
+fill-range@^4.0.0:
+ version "4.0.0"
+ resolved "https://registry.yarnpkg.com/fill-range/-/fill-range-4.0.0.tgz#d544811d428f98eb06a63dc402d2403c328c38f7"
+ integrity sha1-1USBHUKPmOsGpj3EAtJAPDKMOPc=
+ dependencies:
+ extend-shallow "^2.0.1"
+ is-number "^3.0.0"
+ repeat-string "^1.6.1"
+ to-regex-range "^2.1.0"
+
+finalhandler@~1.1.2:
+ version "1.1.2"
+ resolved "https://registry.yarnpkg.com/finalhandler/-/finalhandler-1.1.2.tgz#b7e7d000ffd11938d0fdb053506f6ebabe9f587d"
+ integrity sha512-aAWcW57uxVNrQZqFXjITpW3sIUQmHGG3qSb9mUah9MgMC4NeWhNOlNjXEYq3HjRAvL6arUviZGGJsBg6z0zsWA==
+ dependencies:
+ debug "2.6.9"
+ encodeurl "~1.0.2"
+ escape-html "~1.0.3"
+ on-finished "~2.3.0"
+ parseurl "~1.3.3"
+ statuses "~1.5.0"
+ unpipe "~1.0.0"
+
+for-in@^1.0.2:
+ version "1.0.2"
+ resolved "https://registry.yarnpkg.com/for-in/-/for-in-1.0.2.tgz#81068d295a8142ec0ac726c6e2200c30fb6d5e80"
+ integrity sha1-gQaNKVqBQuwKxybG4iAMMPttXoA=
+
+forwarded@~0.1.2:
+ version "0.1.2"
+ resolved "https://registry.yarnpkg.com/forwarded/-/forwarded-0.1.2.tgz#98c23dab1175657b8c0573e8ceccd91b0ff18c84"
+ integrity sha1-mMI9qxF1ZXuMBXPozszZGw/xjIQ=
+
+fragment-cache@^0.2.1:
+ version "0.2.1"
+ resolved "https://registry.yarnpkg.com/fragment-cache/-/fragment-cache-0.2.1.tgz#4290fad27f13e89be7f33799c6bc5a0abfff0d19"
+ integrity sha1-QpD60n8T6Jvn8zeZxrxaCr//DRk=
+ dependencies:
+ map-cache "^0.2.2"
+
+ version "0.5.2"
+ resolved "https://registry.yarnpkg.com/fresh/-/fresh-0.5.2.tgz#3d8cadd90d976569fa835ab1f8e4b23a105605a7"
+ integrity sha1-PYyt2Q2XZWn6g1qx+OSyOhBWBac=
+
+front-matter@^3.0.2:
+ version "3.2.1"
+ resolved "https://registry.yarnpkg.com/front-matter/-/front-matter-3.2.1.tgz#88be839638f397bbbcb0d61ac03bd08abb4f0a40"
+ integrity sha512-YUhgEhbL6tG+Ok3vTGIoSDKqcr47aSDvyhEqIv8B+YuBJFsPnOiArNXTPp2yO07NL+a0L4+2jXlKlKqyVcsRRA==
+ dependencies:
+ js-yaml "^3.13.1"
+
+fs.realpath@^1.0.0:
+ version "1.0.0"
+ resolved "https://registry.yarnpkg.com/fs.realpath/-/fs.realpath-1.0.0.tgz#1504ad2523158caa40db4a2787cb01411994ea4f"
+ integrity sha1-FQStJSMVjKpA20onh8sBQRmU6k8=
+
+fsevents@^1.2.7:
+ version "1.2.13"
+ resolved "https://registry.yarnpkg.com/fsevents/-/fsevents-1.2.13.tgz#f325cb0455592428bcf11b383370ef70e3bfcc38"
+ integrity sha512-oWb1Z6mkHIskLzEJ/XWX0srkpkTQ7vaopMQkyaEIoq0fmtFVxOthb8cCxeT+p3ynTdkk/RZwbgG4brR5BeWECw==
+ dependencies:
+ bindings "^1.5.0"
+ nan "^2.12.1"
+
+get-stream@^3.0.0:
+ version "3.0.0"
+ resolved "https://registry.yarnpkg.com/get-stream/-/get-stream-3.0.0.tgz#8e943d1358dc37555054ecbe2edb05aa174ede14"
+ integrity sha1-jpQ9E1jcN1VQVOy+LtsFqhdO3hQ=
+
+get-value@^2.0.3, get-value@^2.0.6:
+ version "2.0.6"
+ resolved "https://registry.yarnpkg.com/get-value/-/get-value-2.0.6.tgz#dc15ca1c672387ca76bd37ac0a395ba2042a2c28"
+ integrity sha1-3BXKHGcjh8p2vTesCjlbogQqLCg=
+
+glob-parent@^3.1.0:
+ version "3.1.0"
+ resolved "https://registry.yarnpkg.com/glob-parent/-/glob-parent-3.1.0.tgz#9e6af6299d8d3bd2bd40430832bd113df906c5ae"
+ integrity sha1-nmr2KZ2NO9K9QEMIMr0RPfkGxa4=
+ dependencies:
+ is-glob "^3.1.0"
+ path-dirname "^1.0.0"
+
+glob@^7.1.4:
+ version "7.1.6"
+ resolved "https://registry.yarnpkg.com/glob/-/glob-7.1.6.tgz#141f33b81a7c2492e125594307480c46679278a6"
+ integrity sha512-LwaxwyZ72Lk7vZINtNNrywX0ZuLyStrdDtabefZKAY5ZGJhVtgdznluResxNmPitE0SAO+O26sWTHeKSI2wMBA==
+ dependencies:
+ fs.realpath "^1.0.0"
+ inflight "^1.0.4"
+ inherits "2"
+ minimatch "^3.0.4"
+ once "^1.3.0"
+ path-is-absolute "^1.0.0"
+
+global-dirs@^0.1.0:
+ version "0.1.1"
+ resolved "https://registry.yarnpkg.com/global-dirs/-/global-dirs-0.1.1.tgz#b319c0dd4607f353f3be9cca4c72fc148c49f445"
+ integrity sha1-sxnA3UYH81PzvpzKTHL8FIxJ9EU=
+ dependencies:
+ ini "^1.3.4"
+
+got@^6.7.1:
+ version "6.7.1"
+ resolved "https://registry.yarnpkg.com/got/-/got-6.7.1.tgz#240cd05785a9a18e561dc1b44b41c763ef1e8db0"
+ integrity sha1-JAzQV4WpoY5WHcG0S0HHY+8ejbA=
+ dependencies:
+ create-error-class "^3.0.0"
+ duplexer3 "^0.1.4"
+ get-stream "^3.0.0"
+ is-redirect "^1.0.0"
+ is-retry-allowed "^1.0.0"
+ is-stream "^1.0.0"
+ lowercase-keys "^1.0.0"
+ safe-buffer "^5.0.1"
+ timed-out "^4.0.0"
+ unzip-response "^2.0.1"
+ url-parse-lax "^1.0.0"
+
+graceful-fs@^4.1.11, graceful-fs@^4.1.2:
+ version "4.2.4"
+ resolved "https://registry.yarnpkg.com/graceful-fs/-/graceful-fs-4.2.4.tgz#2256bde14d3632958c465ebc96dc467ca07a29fb"
+ integrity sha512-WjKPNJF79dtJAVniUlGGWHYGz2jWxT6VhN/4m1NdkbZ2nOsEF+cI1Edgql5zCRhs/VsQYRvrXctxktVXZUkixw==
+
+has-flag@^3.0.0:
+ version "3.0.0"
+ resolved "https://registry.yarnpkg.com/has-flag/-/has-flag-3.0.0.tgz#b5d454dc2199ae225699f3467e5a07f3b955bafd"
+ integrity sha1-tdRU3CGZriJWmfNGfloH87lVuv0=
+
+has-value@^0.3.1:
+ version "0.3.1"
+ resolved "https://registry.yarnpkg.com/has-value/-/has-value-0.3.1.tgz#7b1f58bada62ca827ec0a2078025654845995e1f"
+ integrity sha1-ex9YutpiyoJ+wKIHgCVlSEWZXh8=
+ dependencies:
+ get-value "^2.0.3"
+ has-values "^0.1.4"
+ isobject "^2.0.0"
+
+has-value@^1.0.0:
+ version "1.0.0"
+ resolved "https://registry.yarnpkg.com/has-value/-/has-value-1.0.0.tgz#18b281da585b1c5c51def24c930ed29a0be6b177"
+ integrity sha1-GLKB2lhbHFxR3vJMkw7SmgvmsXc=
+ dependencies:
+ get-value "^2.0.6"
+ has-values "^1.0.0"
+ isobject "^3.0.0"
+
+has-values@^0.1.4:
+ version "0.1.4"
+ resolved "https://registry.yarnpkg.com/has-values/-/has-values-0.1.4.tgz#6d61de95d91dfca9b9a02089ad384bff8f62b771"
+ integrity sha1-bWHeldkd/Km5oCCJrThL/49it3E=
+
+has-values@^1.0.0:
+ version "1.0.0"
+ resolved "https://registry.yarnpkg.com/has-values/-/has-values-1.0.0.tgz#95b0b63fec2146619a6fe57fe75628d5a39efe4f"
+ integrity sha1-lbC2P+whRmGab+V/51Yo1aOe/k8=
+ dependencies:
+ is-number "^3.0.0"
+ kind-of "^4.0.0"
+
+highlight.js@^9.3.0:
+ version "9.18.3"
+ resolved "https://registry.yarnpkg.com/highlight.js/-/highlight.js-9.18.3.tgz#a1a0a2028d5e3149e2380f8a865ee8516703d634"
+ integrity sha512-zBZAmhSupHIl5sITeMqIJnYCDfAEc3Gdkqj65wC1lpI468MMQeeQkhcIAvk+RylAkxrCcI9xy9piHiXeQ1BdzQ==
+
+ version "1.7.2"
+ resolved "https://registry.yarnpkg.com/http-errors/-/http-errors-1.7.2.tgz#4f5029cf13239f31036e5b2e55292bcfbcc85c8f"
+ integrity sha512-uUQBt3H/cSIVfch6i1EuPNy/YsRSOUBXTVfZ+yR7Zjez3qjBz6i9+i4zjNaoqcoFVI4lQJ5plg63TvGfRSDCRg==
+ dependencies:
+ depd "~1.1.2"
+ inherits "2.0.3"
+ setprototypeof "1.1.1"
+ statuses ">= 1.5.0 < 2"
+ toidentifier "1.0.0"
+
+http-errors@~1.7.2:
+ version "1.7.3"
+ resolved "https://registry.yarnpkg.com/http-errors/-/http-errors-1.7.3.tgz#6c619e4f9c60308c38519498c14fbb10aacebb06"
+ integrity sha512-ZTTX0MWrsQ2ZAhA1cejAwDLycFsd7I7nVtnkT3Ol0aqodaKW+0CTZDQ1uBv5whptCnc8e8HeRRJxRs0kmm/Qfw==
+ dependencies:
+ depd "~1.1.2"
+ inherits "2.0.4"
+ setprototypeof "1.1.1"
+ statuses ">= 1.5.0 < 2"
+ toidentifier "1.0.0"
+
+ version "0.4.24"
+ resolved "https://registry.yarnpkg.com/iconv-lite/-/iconv-lite-0.4.24.tgz#2022b4b25fbddc21d2f524974a474aafe733908b"
+ integrity sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA==
+ dependencies:
+ safer-buffer ">= 2.1.2 < 3"
+
+ignore-by-default@^1.0.1:
+ version "1.0.1"
+ resolved "https://registry.yarnpkg.com/ignore-by-default/-/ignore-by-default-1.0.1.tgz#48ca6d72f6c6a3af00a9ad4ae6876be3889e2b09"
+ integrity sha1-SMptcvbGo68Aqa1K5odr44ieKwk=
+
+import-lazy@^2.1.0:
+ version "2.1.0"
+ resolved "https://registry.yarnpkg.com/import-lazy/-/import-lazy-2.1.0.tgz#05698e3d45c88e8d7e9d92cb0584e77f096f3e43"
+ integrity sha1-BWmOPUXIjo1+nZLLBYTnfwlvPkM=
+
+imurmurhash@^0.1.4:
+ version "0.1.4"
+ resolved "https://registry.yarnpkg.com/imurmurhash/-/imurmurhash-0.1.4.tgz#9218b9b2b928a238b13dc4fb6b6d576f231453ea"
+ integrity sha1-khi5srkoojixPcT7a21XbyMUU+o=
+
+inflight@^1.0.4:
+ version "1.0.6"
+ resolved "https://registry.yarnpkg.com/inflight/-/inflight-1.0.6.tgz#49bd6331d7d02d0c09bc910a1075ba8165b56df9"
+ integrity sha1-Sb1jMdfQLQwJvJEKEHW6gWW1bfk=
+ dependencies:
+ once "^1.3.0"
+ wrappy "1"
+
+inherits@2, [email protected], inherits@^2.0.3, inherits@~2.0.1, inherits@~2.0.3:
+ version "2.0.4"
+ resolved "https://registry.yarnpkg.com/inherits/-/inherits-2.0.4.tgz#0fa2c64f932917c3433a0ded55363aae37416b7c"
+ integrity sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==
+
+ version "2.0.3"
+ resolved "https://registry.yarnpkg.com/inherits/-/inherits-2.0.3.tgz#633c2c83e3da42a502f52466022480f4208261de"
+ integrity sha1-Yzwsg+PaQqUC9SRmAiSA9CCCYd4=
+
+ini@^1.3.4, ini@~1.3.0:
+ version "1.3.5"
+ resolved "https://registry.yarnpkg.com/ini/-/ini-1.3.5.tgz#eee25f56db1c9ec6085e0c22778083f596abf927"
+ integrity sha512-RZY5huIKCMRWDUqZlEi72f/lmXKMvuszcMBduliQ3nnWbx9X/ZBQO7DijMEYS9EhHBb2qacRUMtC7svLwe0lcw==
+
+ version "1.9.1"
+ resolved "https://registry.yarnpkg.com/ipaddr.js/-/ipaddr.js-1.9.1.tgz#bff38543eeb8984825079ff3a2a8e6cbd46781b3"
+ integrity sha512-0KI/607xoxSToH7GjN1FfSbLoU0+btTicjsQSWQlh/hZykN8KpmMf7uYwPW3R+akZ6R/w18ZlXSHBYXiYUPO3g==
+
+is-accessor-descriptor@^0.1.6:
+ version "0.1.6"
+ resolved "https://registry.yarnpkg.com/is-accessor-descriptor/-/is-accessor-descriptor-0.1.6.tgz#a9e12cb3ae8d876727eeef3843f8a0897b5c98d6"
+ integrity sha1-qeEss66Nh2cn7u84Q/igiXtcmNY=
+ dependencies:
+ kind-of "^3.0.2"
+
+is-accessor-descriptor@^1.0.0:
+ version "1.0.0"
+ resolved "https://registry.yarnpkg.com/is-accessor-descriptor/-/is-accessor-descriptor-1.0.0.tgz#169c2f6d3df1f992618072365c9b0ea1f6878656"
+ integrity sha512-m5hnHTkcVsPfqx3AKlyttIPb7J+XykHvJP2B9bZDjlhLIoEq4XoK64Vg7boZlVWYK6LUY94dYPEE7Lh0ZkZKcQ==
+ dependencies:
+ kind-of "^6.0.0"
+
+is-binary-path@^1.0.0:
+ version "1.0.1"
+ resolved "https://registry.yarnpkg.com/is-binary-path/-/is-binary-path-1.0.1.tgz#75f16642b480f187a711c814161fd3a4a7655898"
+ integrity sha1-dfFmQrSA8YenEcgUFh/TpKdlWJg=
+ dependencies:
+ binary-extensions "^1.0.0"
+
+is-buffer@^1.1.5:
+ version "1.1.6"
+ resolved "https://registry.yarnpkg.com/is-buffer/-/is-buffer-1.1.6.tgz#efaa2ea9daa0d7ab2ea13a97b2b8ad51fefbe8be"
+ integrity sha512-NcdALwpXkTm5Zvvbk7owOUSvVvBKDgKP5/ewfXEznmQFfs4ZRmanOeKBTjRVjka3QFoN6XJ+9F3USqfHqTaU5w==
+
+is-ci@^1.0.10:
+ version "1.2.1"
+ resolved "https://registry.yarnpkg.com/is-ci/-/is-ci-1.2.1.tgz#e3779c8ee17fccf428488f6e281187f2e632841c"
+ integrity sha512-s6tfsaQaQi3JNciBH6shVqEDvhGut0SUXr31ag8Pd8BBbVVlcGfWhpPmEOoM6RJ5TFhbypvf5yyRw/VXW1IiWg==
+ dependencies:
+ ci-info "^1.5.0"
+
+is-data-descriptor@^0.1.4:
+ version "0.1.4"
+ resolved "https://registry.yarnpkg.com/is-data-descriptor/-/is-data-descriptor-0.1.4.tgz#0b5ee648388e2c860282e793f1856fec3f301b56"
+ integrity sha1-C17mSDiOLIYCgueT8YVv7D8wG1Y=
+ dependencies:
+ kind-of "^3.0.2"
+
+is-data-descriptor@^1.0.0:
+ version "1.0.0"
+ resolved "https://registry.yarnpkg.com/is-data-descriptor/-/is-data-descriptor-1.0.0.tgz#d84876321d0e7add03990406abbbbd36ba9268c7"
+ integrity sha512-jbRXy1FmtAoCjQkVmIVYwuuqDFUbaOeDjmed1tOGPrsMhtJA4rD9tkgA0F1qJ3gRFRXcHYVkdeaP50Q5rE/jLQ==
+ dependencies:
+ kind-of "^6.0.0"
+
+is-descriptor@^0.1.0:
+ version "0.1.6"
+ resolved "https://registry.yarnpkg.com/is-descriptor/-/is-descriptor-0.1.6.tgz#366d8240dde487ca51823b1ab9f07a10a78251ca"
+ integrity sha512-avDYr0SB3DwO9zsMov0gKCESFYqCnE4hq/4z3TdUlukEy5t9C0YRq7HLrsN52NAcqXKaepeCD0n+B0arnVG3Hg==
+ dependencies:
+ is-accessor-descriptor "^0.1.6"
+ is-data-descriptor "^0.1.4"
+ kind-of "^5.0.0"
+
+is-descriptor@^1.0.0, is-descriptor@^1.0.2:
+ version "1.0.2"
+ resolved "https://registry.yarnpkg.com/is-descriptor/-/is-descriptor-1.0.2.tgz#3b159746a66604b04f8c81524ba365c5f14d86ec"
+ integrity sha512-2eis5WqQGV7peooDyLmNEPUrps9+SXX5c9pL3xEB+4e9HnGuDa7mB7kHxHw4CbqS9k1T2hOH3miL8n8WtiYVtg==
+ dependencies:
+ is-accessor-descriptor "^1.0.0"
+ is-data-descriptor "^1.0.0"
+ kind-of "^6.0.2"
+
+is-extendable@^0.1.0, is-extendable@^0.1.1:
+ version "0.1.1"
+ resolved "https://registry.yarnpkg.com/is-extendable/-/is-extendable-0.1.1.tgz#62b110e289a471418e3ec36a617d472e301dfc89"
+ integrity sha1-YrEQ4omkcUGOPsNqYX1HLjAd/Ik=
+
+is-extendable@^1.0.1:
+ version "1.0.1"
+ resolved "https://registry.yarnpkg.com/is-extendable/-/is-extendable-1.0.1.tgz#a7470f9e426733d81bd81e1155264e3a3507cab4"
+ integrity sha512-arnXMxT1hhoKo9k1LZdmlNyJdDDfy2v0fXjFlmok4+i8ul/6WlbVge9bhM74OpNPQPMGUToDtz+KXa1PneJxOA==
+ dependencies:
+ is-plain-object "^2.0.4"
+
+is-extglob@^2.1.0, is-extglob@^2.1.1:
+ version "2.1.1"
+ resolved "https://registry.yarnpkg.com/is-extglob/-/is-extglob-2.1.1.tgz#a88c02535791f02ed37c76a1b9ea9773c833f8c2"
+ integrity sha1-qIwCU1eR8C7TfHahueqXc8gz+MI=
+
+is-fullwidth-code-point@^2.0.0:
+ version "2.0.0"
+ resolved "https://registry.yarnpkg.com/is-fullwidth-code-point/-/is-fullwidth-code-point-2.0.0.tgz#a3b30a5c4f199183167aaab93beefae3ddfb654f"
+ integrity sha1-o7MKXE8ZkYMWeqq5O+764937ZU8=
+
+is-glob@^3.1.0:
+ version "3.1.0"
+ resolved "https://registry.yarnpkg.com/is-glob/-/is-glob-3.1.0.tgz#7ba5ae24217804ac70707b96922567486cc3e84a"
+ integrity sha1-e6WuJCF4BKxwcHuWkiVnSGzD6Eo=
+ dependencies:
+ is-extglob "^2.1.0"
+
+is-glob@^4.0.0:
+ version "4.0.1"
+ resolved "https://registry.yarnpkg.com/is-glob/-/is-glob-4.0.1.tgz#7567dbe9f2f5e2467bc77ab83c4a29482407a5dc"
+ integrity sha512-5G0tKtBTFImOqDnLB2hG6Bp2qcKEFduo4tZu9MT/H6NQv/ghhy30o55ufafxJ/LdH79LLs2Kfrn85TLKyA7BUg==
+ dependencies:
+ is-extglob "^2.1.1"
+
+is-installed-globally@^0.1.0:
+ version "0.1.0"
+ resolved "https://registry.yarnpkg.com/is-installed-globally/-/is-installed-globally-0.1.0.tgz#0dfd98f5a9111716dd535dda6492f67bf3d25a80"
+ integrity sha1-Df2Y9akRFxbdU13aZJL2e/PSWoA=
+ dependencies:
+ global-dirs "^0.1.0"
+ is-path-inside "^1.0.0"
+
+is-npm@^1.0.0:
+ version "1.0.0"
+ resolved "https://registry.yarnpkg.com/is-npm/-/is-npm-1.0.0.tgz#f2fb63a65e4905b406c86072765a1a4dc793b9f4"
+ integrity sha1-8vtjpl5JBbQGyGBydloaTceTufQ=
+
+is-number@^3.0.0:
+ version "3.0.0"
+ resolved "https://registry.yarnpkg.com/is-number/-/is-number-3.0.0.tgz#24fd6201a4782cf50561c810276afc7d12d71195"
+ integrity sha1-JP1iAaR4LPUFYcgQJ2r8fRLXEZU=
+ dependencies:
+ kind-of "^3.0.2"
+
+is-obj@^1.0.0:
+ version "1.0.1"
+ resolved "https://registry.yarnpkg.com/is-obj/-/is-obj-1.0.1.tgz#3e4729ac1f5fde025cd7d83a896dab9f4f67db0f"
+ integrity sha1-PkcprB9f3gJc19g6iW2rn09n2w8=
+
+is-path-inside@^1.0.0:
+ version "1.0.1"
+ resolved "https://registry.yarnpkg.com/is-path-inside/-/is-path-inside-1.0.1.tgz#8ef5b7de50437a3fdca6b4e865ef7aa55cb48036"
+ integrity sha1-jvW33lBDej/cprToZe96pVy0gDY=
+ dependencies:
+ path-is-inside "^1.0.1"
+
+is-plain-object@^2.0.3, is-plain-object@^2.0.4:
+ version "2.0.4"
+ resolved "https://registry.yarnpkg.com/is-plain-object/-/is-plain-object-2.0.4.tgz#2c163b3fafb1b606d9d17928f05c2a1c38e07677"
+ integrity sha512-h5PpgXkWitc38BBMYawTYMWJHFZJVnBquFE57xFpjB8pJFiF6gZ+bU+WyI/yqXiFR5mdLsgYNaPe8uao6Uv9Og==
+ dependencies:
+ isobject "^3.0.1"
+
+is-redirect@^1.0.0:
+ version "1.0.0"
+ resolved "https://registry.yarnpkg.com/is-redirect/-/is-redirect-1.0.0.tgz#1d03dded53bd8db0f30c26e4f95d36fc7c87dc24"
+ integrity sha1-HQPd7VO9jbDzDCbk+V02/HyH3CQ=
+
+is-retry-allowed@^1.0.0:
+ version "1.2.0"
+ resolved "https://registry.yarnpkg.com/is-retry-allowed/-/is-retry-allowed-1.2.0.tgz#d778488bd0a4666a3be8a1482b9f2baafedea8b4"
+ integrity sha512-RUbUeKwvm3XG2VYamhJL1xFktgjvPzL0Hq8C+6yrWIswDy3BIXGqCxhxkc30N9jqK311gVU137K8Ei55/zVJRg==
+
+is-stream@^1.0.0, is-stream@^1.1.0:
+ version "1.1.0"
+ resolved "https://registry.yarnpkg.com/is-stream/-/is-stream-1.1.0.tgz#12d4a3dd4e68e0b79ceb8dbc84173ae80d91ca44"
+ integrity sha1-EtSj3U5o4Lec6428hBc66A2RykQ=
+
+is-windows@^1.0.2:
+ version "1.0.2"
+ resolved "https://registry.yarnpkg.com/is-windows/-/is-windows-1.0.2.tgz#d1850eb9791ecd18e6182ce12a30f396634bb19d"
+ integrity sha512-eXK1UInq2bPmjyX6e3VHIzMLobc4J94i4AWn+Hpq3OU5KkrRC96OAcR3PRJ/pGu6m8TRnBHP9dkXQVsT/COVIA==
+
+ version "0.0.1"
+ resolved "https://registry.yarnpkg.com/isarray/-/isarray-0.0.1.tgz#8a18acfca9a8f4177e09abfc6038939b05d1eedf"
+ integrity sha1-ihis/Kmo9Bd+Cav8YDiTmwXR7t8=
+
[email protected], isarray@~1.0.0:
+ version "1.0.0"
+ resolved "https://registry.yarnpkg.com/isarray/-/isarray-1.0.0.tgz#bb935d48582cba168c06834957a54a3e07124f11"
+ integrity sha1-u5NdSFgsuhaMBoNJV6VKPgcSTxE=
+
+isexe@^2.0.0:
+ version "2.0.0"
+ resolved "https://registry.yarnpkg.com/isexe/-/isexe-2.0.0.tgz#e8fbf374dc556ff8947a10dcb0572d633f2cfa10"
+ integrity sha1-6PvzdNxVb/iUehDcsFctYz8s+hA=
+
+isobject@^2.0.0:
+ version "2.1.0"
+ resolved "https://registry.yarnpkg.com/isobject/-/isobject-2.1.0.tgz#f065561096a3f1da2ef46272f815c840d87e0c89"
+ integrity sha1-8GVWEJaj8dou9GJy+BXIQNh+DIk=
+ dependencies:
+ isarray "1.0.0"
+
+isobject@^3.0.0, isobject@^3.0.1:
+ version "3.0.1"
+ resolved "https://registry.yarnpkg.com/isobject/-/isobject-3.0.1.tgz#4e431e92b11a9731636aa1f9c8d1ccbcfdab78df"
+ integrity sha1-TkMekrEalzFjaqH5yNHMvP2reN8=
+
+js-yaml@^3.13.1:
+ version "3.14.0"
+ resolved "https://registry.yarnpkg.com/js-yaml/-/js-yaml-3.14.0.tgz#a7a34170f26a21bb162424d8adacb4113a69e482"
+ integrity sha512-/4IbIeHcD9VMHFqDR/gQ7EdZdLimOvW2DdcxFjdyyZ9NsbS+ccrXqVWDtab/lRl5AlUqmpBx8EhPaWR+OtY17A==
+ dependencies:
+ argparse "^1.0.7"
+ esprima "^4.0.0"
+
+json-schema-traverse@^0.4.1:
+ version "0.4.1"
+ resolved "https://registry.yarnpkg.com/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz#69f6a87d9513ab8bb8fe63bdb0979c448e684660"
+ integrity sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==
+
+katex@^0.10.2:
+ version "0.10.2"
+ resolved "https://registry.yarnpkg.com/katex/-/katex-0.10.2.tgz#39973edbb65eda5b6f9e7f41648781e557dd4932"
+ integrity sha512-cQOmyIRoMloCoSIOZ1+gEwsksdJZ1EW4SWm3QzxSza/QsnZr6D4U1V9S4q+B/OLm2OQ8TCBecQ8MaIfnScI7cw==
+ dependencies:
+ commander "^2.19.0"
+
+kind-of@^3.0.2, kind-of@^3.0.3, kind-of@^3.2.0:
+ version "3.2.2"
+ resolved "https://registry.yarnpkg.com/kind-of/-/kind-of-3.2.2.tgz#31ea21a734bab9bbb0f32466d893aea51e4a3c64"
+ integrity sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=
+ dependencies:
+ is-buffer "^1.1.5"
+
+kind-of@^4.0.0:
+ version "4.0.0"
+ resolved "https://registry.yarnpkg.com/kind-of/-/kind-of-4.0.0.tgz#20813df3d712928b207378691a45066fae72dd57"
+ integrity sha1-IIE989cSkosgc3hpGkUGb65y3Vc=
+ dependencies:
+ is-buffer "^1.1.5"
+
+kind-of@^5.0.0:
+ version "5.1.0"
+ resolved "https://registry.yarnpkg.com/kind-of/-/kind-of-5.1.0.tgz#729c91e2d857b7a419a1f9aa65685c4c33f5845d"
+ integrity sha512-NGEErnH6F2vUuXDh+OlbcKW7/wOcfdRHaZ7VWtqCztfHri/++YKmP51OdWeGPuqCOba6kk2OTe5d02VmTB80Pw==
+
+kind-of@^6.0.0, kind-of@^6.0.2:
+ version "6.0.3"
+ resolved "https://registry.yarnpkg.com/kind-of/-/kind-of-6.0.3.tgz#07c05034a6c349fa06e24fa35aa76db4580ce4dd"
+ integrity sha512-dcS1ul+9tmeD95T+x28/ehLgd9mENa3LsvDTtzm3vyBEO7RPptvAD+t44WVXaUjTBRcrpFeFlC8WCruUR456hw==
+
+latest-version@^3.0.0:
+ version "3.1.0"
+ resolved "https://registry.yarnpkg.com/latest-version/-/latest-version-3.1.0.tgz#a205383fea322b33b5ae3b18abee0dc2f356ee15"
+ integrity sha1-ogU4P+oyKzO1rjsYq+4NwvNW7hU=
+ dependencies:
+ package-json "^4.0.0"
+
+lowercase-keys@^1.0.0:
+ version "1.0.1"
+ resolved "https://registry.yarnpkg.com/lowercase-keys/-/lowercase-keys-1.0.1.tgz#6f9e30b47084d971a7c820ff15a6c5167b74c26f"
+ integrity sha512-G2Lj61tXDnVFFOi8VZds+SoQjtQC3dgokKdDG2mTm1tx4m50NUHBOZSBwQQHyy0V12A0JTG4icfZQH+xPyh8VA==
+
+lru-cache@^4.0.1:
+ version "4.1.5"
+ resolved "https://registry.yarnpkg.com/lru-cache/-/lru-cache-4.1.5.tgz#8bbe50ea85bed59bc9e33dcab8235ee9bcf443cd"
+ integrity sha512-sWZlbEP2OsHNkXrMl5GYk/jKk70MBng6UU4YI/qGDYbgf6YbP4EvmqISbXCoJiRKs+1bSpFHVgQxvJ17F2li5g==
+ dependencies:
+ pseudomap "^1.0.2"
+ yallist "^2.1.2"
+
+make-dir@^1.0.0:
+ version "1.3.0"
+ resolved "https://registry.yarnpkg.com/make-dir/-/make-dir-1.3.0.tgz#79c1033b80515bd6d24ec9933e860ca75ee27f0c"
+ integrity sha512-2w31R7SJtieJJnQtGc7RVL2StM2vGYVfqUOvUDxH6bC6aJTxPxTF0GnIgCyu7tjockiUWAYQRbxa7vKn34s5sQ==
+ dependencies:
+ pify "^3.0.0"
+
+map-cache@^0.2.2:
+ version "0.2.2"
+ resolved "https://registry.yarnpkg.com/map-cache/-/map-cache-0.2.2.tgz#c32abd0bd6525d9b051645bb4f26ac5dc98a0dbf"
+ integrity sha1-wyq9C9ZSXZsFFkW7TyasXcmKDb8=
+
+map-visit@^1.0.0:
+ version "1.0.0"
+ resolved "https://registry.yarnpkg.com/map-visit/-/map-visit-1.0.0.tgz#ecdca8f13144e660f1b5bd41f12f3479d98dfb8f"
+ integrity sha1-7Nyo8TFE5mDxtb1B8S80edmN+48=
+ dependencies:
+ object-visit "^1.0.0"
+
+marked@^0.6.2:
+ version "0.6.3"
+ resolved "https://registry.yarnpkg.com/marked/-/marked-0.6.3.tgz#79babad78af638ba4d522a9e715cdfdd2429e946"
+ integrity sha512-Fqa7eq+UaxfMriqzYLayfqAE40WN03jf+zHjT18/uXNuzjq3TY0XTbrAoPeqSJrAmPz11VuUA+kBPYOhHt9oOQ==
+
+ version "0.3.0"
+ resolved "https://registry.yarnpkg.com/media-typer/-/media-typer-0.3.0.tgz#8710d7af0aa626f8fffa1ce00168545263255748"
+ integrity sha1-hxDXrwqmJvj/+hzgAWhUUmMlV0g=
+
+ version "1.0.1"
+ resolved "https://registry.yarnpkg.com/merge-descriptors/-/merge-descriptors-1.0.1.tgz#b00aaa556dd8b44568150ec9d1b953f3f90cbb61"
+ integrity sha1-sAqqVW3YtEVoFQ7J0blT8/kMu2E=
+
+methods@~1.1.2:
+ version "1.1.2"
+ resolved "https://registry.yarnpkg.com/methods/-/methods-1.1.2.tgz#5529a4d67654134edcc5266656835b0f851afcee"
+ integrity sha1-VSmk1nZUE07cxSZmVoNbD4Ua/O4=
+
+micromatch@^3.1.10, micromatch@^3.1.4:
+ version "3.1.10"
+ resolved "https://registry.yarnpkg.com/micromatch/-/micromatch-3.1.10.tgz#70859bc95c9840952f359a068a3fc49f9ecfac23"
+ integrity sha512-MWikgl9n9M3w+bpsY3He8L+w9eF9338xRl8IAO5viDizwSzziFEyUzo2xrrloB64ADbTf8uA8vRqqttDTOmccg==
+ dependencies:
+ arr-diff "^4.0.0"
+ array-unique "^0.3.2"
+ braces "^2.3.1"
+ define-property "^2.0.2"
+ extend-shallow "^3.0.2"
+ extglob "^2.0.4"
+ fragment-cache "^0.2.1"
+ kind-of "^6.0.2"
+ nanomatch "^1.2.9"
+ object.pick "^1.3.0"
+ regex-not "^1.0.0"
+ snapdragon "^0.8.1"
+ to-regex "^3.0.2"
+
+ version "1.44.0"
+ resolved "https://registry.yarnpkg.com/mime-db/-/mime-db-1.44.0.tgz#fa11c5eb0aca1334b4233cb4d52f10c5a6272f92"
+ integrity sha512-/NOTfLrsPBVeH7YtFPgsVWveuL+4SjjYxaQ1xtM1KMFj7HdxlBlxeyNLzhyJVx7r4rZGJAZ/6lkKCitSc/Nmpg==
+
+"mime-db@>= 1.43.0 < 2":
+ version "1.45.0"
+ resolved "https://registry.yarnpkg.com/mime-db/-/mime-db-1.45.0.tgz#cceeda21ccd7c3a745eba2decd55d4b73e7879ea"
+ integrity sha512-CkqLUxUk15hofLoLyljJSrukZi8mAtgd+yE5uO4tqRZsdsAJKv0O+rFMhVDRJgozy+yG6md5KwuXhD4ocIoP+w==
+
+mime-db@~1.33.0:
+ version "1.33.0"
+ resolved "https://registry.yarnpkg.com/mime-db/-/mime-db-1.33.0.tgz#a3492050a5cb9b63450541e39d9788d2272783db"
+ integrity sha512-BHJ/EKruNIqJf/QahvxwQZXKygOQ256myeN/Ew+THcAa5q+PjyTTMMeNQC4DZw5AwfvelsUrA6B67NKMqXDbzQ==
+
+ version "2.1.18"
+ resolved "https://registry.yarnpkg.com/mime-types/-/mime-types-2.1.18.tgz#6f323f60a83d11146f831ff11fd66e2fe5503bb8"
+ integrity sha512-lc/aahn+t4/SWV/qcmumYjymLsWfN3ELhpmVuUFjgsORruuZPVSwAQryq+HHGvO/SI2KVX26bx+En+zhM8g8hQ==
+ dependencies:
+ mime-db "~1.33.0"
+
+mime-types@~2.1.24:
+ version "2.1.27"
+ resolved "https://registry.yarnpkg.com/mime-types/-/mime-types-2.1.27.tgz#47949f98e279ea53119f5722e0f34e529bec009f"
+ integrity sha512-JIhqnCasI9yD+SsmkquHBxTSEuZdQX5BuQnS2Vc7puQQQ+8yiP5AY5uWhpdv4YL4VM5c6iliiYWPgJ/nJQLp7w==
+ dependencies:
+ mime-db "1.44.0"
+
+ version "1.6.0"
+ resolved "https://registry.yarnpkg.com/mime/-/mime-1.6.0.tgz#32cd9e5c64553bd58d19a568af452acff04981b1"
+ integrity sha512-x0Vn8spI+wuJ1O6S7gnbaQg8Pxh4NNHb7KSINmEWKiPE4RKOplvijn+NkmYmmRgP68mc70j2EbeTFRsrswaQeg==
+
[email protected], minimatch@^3.0.4:
+ version "3.0.4"
+ resolved "https://registry.yarnpkg.com/minimatch/-/minimatch-3.0.4.tgz#5166e286457f03306064be5497e8dbb0c3d32083"
+ integrity sha512-yJHVQEhyqPLUTgt9B83PXu6W3rx4MvvHvSUvToogpwoGDOUQ+yDrR0HRot+yOCdCO7u4hX3pWft6kWBBcqh0UA==
+ dependencies:
+ brace-expansion "^1.1.7"
+
+minimist@^1.2.0, minimist@^1.2.5:
+ version "1.2.5"
+ resolved "https://registry.yarnpkg.com/minimist/-/minimist-1.2.5.tgz#67d66014b66a6a8aaa0c083c5fd58df4e4e97602"
+ integrity sha512-FM9nNUYrRBAELZQT3xeZQ7fmMOBg6nWNmJKTcgsJeaLstP/UODVpGsr5OhXhhXg6f+qtJ8uiZ+PUxkDWcgIXLw==
+
+mixin-deep@^1.2.0:
+ version "1.3.2"
+ resolved "https://registry.yarnpkg.com/mixin-deep/-/mixin-deep-1.3.2.tgz#1120b43dc359a785dce65b55b82e257ccf479566"
+ integrity sha512-WRoDn//mXBiJ1H40rqa3vH0toePwSsGb45iInWlTySa+Uu4k3tYUSxa2v1KqAiLtvlrSzaExqS1gtk96A9zvEA==
+ dependencies:
+ for-in "^1.0.2"
+ is-extendable "^1.0.1"
+
+mkdirp@^0.5.1:
+ version "0.5.5"
+ resolved "https://registry.yarnpkg.com/mkdirp/-/mkdirp-0.5.5.tgz#d91cefd62d1436ca0f41620e251288d420099def"
+ integrity sha512-NKmAlESf6jMGym1++R0Ra7wvhV+wFW63FaSOFPwRahvea0gMUcGUhVeAg/0BC0wiv9ih5NYPB1Wn1UEI1/L+xQ==
+ dependencies:
+ minimist "^1.2.5"
+
+ version "2.0.0"
+ resolved "https://registry.yarnpkg.com/ms/-/ms-2.0.0.tgz#5608aeadfc00be6c2901df5f9861788de0d597c8"
+ integrity sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=
+
+ version "2.1.1"
+ resolved "https://registry.yarnpkg.com/ms/-/ms-2.1.1.tgz#30a5864eb3ebb0a66f2ebe6d727af06a09d86e0a"
+ integrity sha512-tgp+dl5cGk28utYktBsrFqA7HKgrhgPsg6Z/EfhWI4gl1Hwq8B/GmY/0oXZ6nF8hDVesS/FpnYaD/kOWhYQvyg==
+
+ms@^2.1.1:
+ version "2.1.2"
+ resolved "https://registry.yarnpkg.com/ms/-/ms-2.1.2.tgz#d09d1f357b443f493382a8eb3ccd183872ae6009"
+ integrity sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==
+
+multer@^1.3.0:
+ version "1.4.2"
+ resolved "https://registry.yarnpkg.com/multer/-/multer-1.4.2.tgz#2f1f4d12dbaeeba74cb37e623f234bf4d3d2057a"
+ integrity sha512-xY8pX7V+ybyUpbYMxtjM9KAiD9ixtg5/JkeKUTD6xilfDv0vzzOFcCp4Ljb1UU3tSOM3VTZtKo63OmzOrGi3Cg==
+ dependencies:
+ append-field "^1.0.0"
+ busboy "^0.2.11"
+ concat-stream "^1.5.2"
+ mkdirp "^0.5.1"
+ object-assign "^4.1.1"
+ on-finished "^2.3.0"
+ type-is "^1.6.4"
+ xtend "^4.0.0"
+
+nan@^2.12.1:
+ version "2.14.2"
+ resolved "https://registry.yarnpkg.com/nan/-/nan-2.14.2.tgz#f5376400695168f4cc694ac9393d0c9585eeea19"
+ integrity sha512-M2ufzIiINKCuDfBSAUr1vWQ+vuVcA9kqx8JJUsbQi6yf1uGRyb7HfpdfUr5qLXf3B/t8dPvcjhKMmlfnP47EzQ==
+
+nanomatch@^1.2.9:
+ version "1.2.13"
+ resolved "https://registry.yarnpkg.com/nanomatch/-/nanomatch-1.2.13.tgz#b87a8aa4fc0de8fe6be88895b38983ff265bd119"
+ integrity sha512-fpoe2T0RbHwBTBUOftAfBPaDEi06ufaUai0mE6Yn1kacc3SnTErfb/h+X94VXzI64rKFHYImXSvdwGGCmwOqCA==
+ dependencies:
+ arr-diff "^4.0.0"
+ array-unique "^0.3.2"
+ define-property "^2.0.2"
+ extend-shallow "^3.0.2"
+ fragment-cache "^0.2.1"
+ is-windows "^1.0.2"
+ kind-of "^6.0.2"
+ object.pick "^1.3.0"
+ regex-not "^1.0.0"
+ snapdragon "^0.8.1"
+ to-regex "^3.0.1"
+
+ncp@^2.0.0:
+ version "2.0.0"
+ resolved "https://registry.yarnpkg.com/ncp/-/ncp-2.0.0.tgz#195a21d6c46e361d2fb1281ba38b91e9df7bdbb3"
+ integrity sha1-GVoh1sRuNh0vsSgbo4uR6d9727M=
+
+ version "0.6.2"
+ resolved "https://registry.yarnpkg.com/negotiator/-/negotiator-0.6.2.tgz#feacf7ccf525a77ae9634436a64883ffeca346fb"
+ integrity sha512-hZXc7K2e+PgeI1eDBe/10Ard4ekbfrrqG8Ep+8Jmf4JID2bNg7NvCPOZN+kfF574pFQI7mum2AUqDidoKqcTOw==
+
+nodemon@^1.19.0:
+ version "1.19.4"
+ resolved "https://registry.yarnpkg.com/nodemon/-/nodemon-1.19.4.tgz#56db5c607408e0fdf8920d2b444819af1aae0971"
+ integrity sha512-VGPaqQBNk193lrJFotBU8nvWZPqEZY2eIzymy2jjY0fJ9qIsxA0sxQ8ATPl0gZC645gijYEc1jtZvpS8QWzJGQ==
+ dependencies:
+ chokidar "^2.1.8"
+ debug "^3.2.6"
+ ignore-by-default "^1.0.1"
+ minimatch "^3.0.4"
+ pstree.remy "^1.1.7"
+ semver "^5.7.1"
+ supports-color "^5.5.0"
+ touch "^3.1.0"
+ undefsafe "^2.0.2"
+ update-notifier "^2.5.0"
+
+nopt@~1.0.10:
+ version "1.0.10"
+ resolved "https://registry.yarnpkg.com/nopt/-/nopt-1.0.10.tgz#6ddd21bd2a31417b92727dd585f8a6f37608ebee"
+ integrity sha1-bd0hvSoxQXuScn3Vhfim83YI6+4=
+ dependencies:
+ abbrev "1"
+
+normalize-path@^2.1.1:
+ version "2.1.1"
+ resolved "https://registry.yarnpkg.com/normalize-path/-/normalize-path-2.1.1.tgz#1ab28b556e198363a8c1a6f7e6fa20137fe6aed9"
+ integrity sha1-GrKLVW4Zg2Oowab35vogE3/mrtk=
+ dependencies:
+ remove-trailing-separator "^1.0.1"
+
+normalize-path@^3.0.0:
+ version "3.0.0"
+ resolved "https://registry.yarnpkg.com/normalize-path/-/normalize-path-3.0.0.tgz#0dcd69ff23a1c9b11fd0978316644a0388216a65"
+ integrity sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==
+
+npm-run-path@^2.0.0:
+ version "2.0.2"
+ resolved "https://registry.yarnpkg.com/npm-run-path/-/npm-run-path-2.0.2.tgz#35a9232dfa35d7067b4cb2ddf2357b1871536c5f"
+ integrity sha1-NakjLfo11wZ7TLLd8jV7GHFTbF8=
+ dependencies:
+ path-key "^2.0.0"
+
+object-assign@^4.1.1:
+ version "4.1.1"
+ resolved "https://registry.yarnpkg.com/object-assign/-/object-assign-4.1.1.tgz#2109adc7965887cfc05cbbd442cac8bfbb360863"
+ integrity sha1-IQmtx5ZYh8/AXLvUQsrIv7s2CGM=
+
+object-copy@^0.1.0:
+ version "0.1.0"
+ resolved "https://registry.yarnpkg.com/object-copy/-/object-copy-0.1.0.tgz#7e7d858b781bd7c991a41ba975ed3812754e998c"
+ integrity sha1-fn2Fi3gb18mRpBupde04EnVOmYw=
+ dependencies:
+ copy-descriptor "^0.1.0"
+ define-property "^0.2.5"
+ kind-of "^3.0.3"
+
+object-visit@^1.0.0:
+ version "1.0.1"
+ resolved "https://registry.yarnpkg.com/object-visit/-/object-visit-1.0.1.tgz#f79c4493af0c5377b59fe39d395e41042dd045bb"
+ integrity sha1-95xEk68MU3e1n+OdOV5BBC3QRbs=
+ dependencies:
+ isobject "^3.0.0"
+
+object.pick@^1.3.0:
+ version "1.3.0"
+ resolved "https://registry.yarnpkg.com/object.pick/-/object.pick-1.3.0.tgz#87a10ac4c1694bd2e1cbf53591a66141fb5dd747"
+ integrity sha1-h6EKxMFpS9Lhy/U1kaZhQftd10c=
+ dependencies:
+ isobject "^3.0.1"
+
+on-finished@^2.3.0, on-finished@~2.3.0:
+ version "2.3.0"
+ resolved "https://registry.yarnpkg.com/on-finished/-/on-finished-2.3.0.tgz#20f1336481b083cd75337992a16971aa2d906947"
+ integrity sha1-IPEzZIGwg811M3mSoWlxqi2QaUc=
+ dependencies:
+ ee-first "1.1.1"
+
+on-headers@~1.0.1:
+ version "1.0.2"
+ resolved "https://registry.yarnpkg.com/on-headers/-/on-headers-1.0.2.tgz#772b0ae6aaa525c399e489adfad90c403eb3c28f"
+ integrity sha512-pZAE+FJLoyITytdqK0U5s+FIpjN0JP3OzFi/u8Rx+EV5/W+JTWGXG8xFzevE7AjBfDqHv/8vL8qQsIhHnqRkrA==
+
+once@^1.3.0:
+ version "1.4.0"
+ resolved "https://registry.yarnpkg.com/once/-/once-1.4.0.tgz#583b1aa775961d4b113ac17d9c50baef9dd76bd1"
+ integrity sha1-WDsap3WWHUsROsF9nFC6753Xa9E=
+ dependencies:
+ wrappy "1"
+
+p-finally@^1.0.0:
+ version "1.0.0"
+ resolved "https://registry.yarnpkg.com/p-finally/-/p-finally-1.0.0.tgz#3fbcfb15b899a44123b34b6dcc18b724336a2cae"
+ integrity sha1-P7z7FbiZpEEjs0ttzBi3JDNqLK4=
+
+package-json@^4.0.0:
+ version "4.0.1"
+ resolved "https://registry.yarnpkg.com/package-json/-/package-json-4.0.1.tgz#8869a0401253661c4c4ca3da6c2121ed555f5eed"
+ integrity sha1-iGmgQBJTZhxMTKPabCEh7VVfXu0=
+ dependencies:
+ got "^6.7.1"
+ registry-auth-token "^3.0.1"
+ registry-url "^3.0.3"
+ semver "^5.1.0"
+
+parseurl@~1.3.3:
+ version "1.3.3"
+ resolved "https://registry.yarnpkg.com/parseurl/-/parseurl-1.3.3.tgz#9da19e7bee8d12dff0513ed5b76957793bc2e8d4"
+ integrity sha512-CiyeOxFT/JZyN5m0z9PfXw4SCBJ6Sygz1Dpl0wqjlhDEGGBP1GnsUVEL0p63hoG1fcj3fHynXi9NYO4nWOL+qQ==
+
+pascalcase@^0.1.1:
+ version "0.1.1"
+ resolved "https://registry.yarnpkg.com/pascalcase/-/pascalcase-0.1.1.tgz#b363e55e8006ca6fe21784d2db22bd15d7917f14"
+ integrity sha1-s2PlXoAGym/iF4TS2yK9FdeRfxQ=
+
+path-dirname@^1.0.0:
+ version "1.0.2"
+ resolved "https://registry.yarnpkg.com/path-dirname/-/path-dirname-1.0.2.tgz#cc33d24d525e099a5388c0336c6e32b9160609e0"
+ integrity sha1-zDPSTVJeCZpTiMAzbG4yuRYGCeA=
+
+path-is-absolute@^1.0.0:
+ version "1.0.1"
+ resolved "https://registry.yarnpkg.com/path-is-absolute/-/path-is-absolute-1.0.1.tgz#174b9268735534ffbc7ace6bf53a5a9e1b5c5f5f"
+ integrity sha1-F0uSaHNVNP+8es5r9TpanhtcX18=
+
[email protected], path-is-inside@^1.0.1:
+ version "1.0.2"
+ resolved "https://registry.yarnpkg.com/path-is-inside/-/path-is-inside-1.0.2.tgz#365417dede44430d1c11af61027facf074bdfc53"
+ integrity sha1-NlQX3t5EQw0cEa9hAn+s8HS9/FM=
+
+path-key@^2.0.0:
+ version "2.0.1"
+ resolved "https://registry.yarnpkg.com/path-key/-/path-key-2.0.1.tgz#411cadb574c5a140d3a4b1910d40d80cc9f40b40"
+ integrity sha1-QRyttXTFoUDTpLGRDUDYDMn0C0A=
+
+ version "0.1.7"
+ resolved "https://registry.yarnpkg.com/path-to-regexp/-/path-to-regexp-0.1.7.tgz#df604178005f522f15eb4490e7247a1bfaa67f8c"
+ integrity sha1-32BBeABfUi8V60SQ5yR6G/qmf4w=
+
+ version "2.2.1"
+ resolved "https://registry.yarnpkg.com/path-to-regexp/-/path-to-regexp-2.2.1.tgz#90b617025a16381a879bc82a38d4e8bdeb2bcf45"
+ integrity sha512-gu9bD6Ta5bwGrrU8muHzVOBFFREpp2iRkVfhBJahwJ6p6Xw20SjT0MxLnwkjOibQmGSYhiUnf2FLe7k+jcFmGQ==
+
+pify@^3.0.0:
+ version "3.0.0"
+ resolved "https://registry.yarnpkg.com/pify/-/pify-3.0.0.tgz#e5a4acd2c101fdf3d9a4d07f0dbc4db49dd28176"
+ integrity sha1-5aSs0sEB/fPZpNB/DbxNtJ3SgXY=
+
+posix-character-classes@^0.1.0:
+ version "0.1.1"
+ resolved "https://registry.yarnpkg.com/posix-character-classes/-/posix-character-classes-0.1.1.tgz#01eac0fe3b5af71a2a6c02feabb8c1fef7e00eab"
+ integrity sha1-AerA/jta9xoqbAL+q7jB/vfgDqs=
+
+prepend-http@^1.0.1:
+ version "1.0.4"
+ resolved "https://registry.yarnpkg.com/prepend-http/-/prepend-http-1.0.4.tgz#d4f4562b0ce3696e41ac52d0e002e57a635dc6dc"
+ integrity sha1-1PRWKwzjaW5BrFLQ4ALlemNdxtw=
+
+process-nextick-args@~2.0.0:
+ version "2.0.1"
+ resolved "https://registry.yarnpkg.com/process-nextick-args/-/process-nextick-args-2.0.1.tgz#7820d9b16120cc55ca9ae7792680ae7dba6d7fe2"
+ integrity sha512-3ouUOpQhtgrbOa17J7+uxOTpITYWaGP7/AhoR3+A+/1e9skrzelGi/dXzEYyvbxubEF6Wn2ypscTKiKJFFn1ag==
+
+proxy-addr@~2.0.5:
+ version "2.0.6"
+ resolved "https://registry.yarnpkg.com/proxy-addr/-/proxy-addr-2.0.6.tgz#fdc2336505447d3f2f2c638ed272caf614bbb2bf"
+ integrity sha512-dh/frvCBVmSsDYzw6n926jv974gddhkFPfiN8hPOi30Wax25QZyZEGveluCgliBnqmuM+UJmBErbAUFIoDbjOw==
+ dependencies:
+ forwarded "~0.1.2"
+ ipaddr.js "1.9.1"
+
+pseudomap@^1.0.2:
+ version "1.0.2"
+ resolved "https://registry.yarnpkg.com/pseudomap/-/pseudomap-1.0.2.tgz#f052a28da70e618917ef0a8ac34c1ae5a68286b3"
+ integrity sha1-8FKijacOYYkX7wqKw0wa5aaChrM=
+
+pstree.remy@^1.1.7:
+ version "1.1.8"
+ resolved "https://registry.yarnpkg.com/pstree.remy/-/pstree.remy-1.1.8.tgz#c242224f4a67c21f686839bbdb4ac282b8373d3a"
+ integrity sha512-77DZwxQmxKnu3aR542U+X8FypNzbfJ+C5XQDk3uWjWxn6151aIMGthWYRXTqT1E5oJvg+ljaa2OJi+VfvCOQ8w==
+
+punycode@^1.3.2:
+ version "1.4.1"
+ resolved "https://registry.yarnpkg.com/punycode/-/punycode-1.4.1.tgz#c0d5a63b2718800ad8e1eb0fa5269c84dd41845e"
+ integrity sha1-wNWmOycYgArY4esPpSachN1BhF4=
+
+punycode@^2.1.0:
+ version "2.1.1"
+ resolved "https://registry.yarnpkg.com/punycode/-/punycode-2.1.1.tgz#b58b010ac40c22c5657616c8d2c2c02c7bf479ec"
+ integrity sha512-XRsRjdf+j5ml+y/6GKHPZbrF/8p2Yga0JPtdqTIY2Xe5ohJPD9saDJJLPvp9+NSBprVvevdXZybnj2cv8OEd0A==
+
+ version "6.7.0"
+ resolved "https://registry.yarnpkg.com/qs/-/qs-6.7.0.tgz#41dc1a015e3d581f1621776be31afb2876a9b1bc"
+ integrity sha512-VCdBRNFTX1fyE7Nb6FYoURo/SPe62QCaAyzJvUjwRaIsc+NePBEniHlvxFmmX56+HZphIGtV0XeCirBtpDrTyQ==
+
+ version "1.2.0"
+ resolved "https://registry.yarnpkg.com/range-parser/-/range-parser-1.2.0.tgz#f49be6b487894ddc40dcc94a322f611092e00d5e"
+ integrity sha1-9JvmtIeJTdxA3MlKMi9hEJLgDV4=
+
+range-parser@~1.2.1:
+ version "1.2.1"
+ resolved "https://registry.yarnpkg.com/range-parser/-/range-parser-1.2.1.tgz#3cf37023d199e1c24d1a55b84800c2f3e6468031"
+ integrity sha512-Hrgsx+orqoygnmhFbKaHE6c296J+HTAQXoxEF6gNupROmmGJRoyzfG3ccAveqCBrwr/2yxQ5BVd/GTl5agOwSg==
+
+ version "2.4.0"
+ resolved "https://registry.yarnpkg.com/raw-body/-/raw-body-2.4.0.tgz#a1ce6fb9c9bc356ca52e89256ab59059e13d0332"
+ integrity sha512-4Oz8DUIwdvoa5qMJelxipzi/iJIi40O5cGV1wNYp5hvZP8ZN0T+jiNkL0QepXs+EsQ9XJ8ipEDoiH70ySUJP3Q==
+ dependencies:
+ bytes "3.1.0"
+ http-errors "1.7.2"
+ iconv-lite "0.4.24"
+ unpipe "1.0.0"
+
+rc@^1.0.1, rc@^1.1.6:
+ version "1.2.8"
+ resolved "https://registry.yarnpkg.com/rc/-/rc-1.2.8.tgz#cd924bf5200a075b83c188cd6b9e211b7fc0d3ed"
+ integrity sha512-y3bGgqKj3QBdxLbLkomlohkvsA8gdAiUQlSBJnBhfn+BPxg4bc62d8TcBW15wavDfgexCgccckhcZvywyQYPOw==
+ dependencies:
+ deep-extend "^0.6.0"
+ ini "~1.3.0"
+ minimist "^1.2.0"
+ strip-json-comments "~2.0.1"
+
+ version "1.1.14"
+ resolved "https://registry.yarnpkg.com/readable-stream/-/readable-stream-1.1.14.tgz#7cf4c54ef648e3813084c636dd2079e166c081d9"
+ integrity sha1-fPTFTvZI44EwhMY23SB54WbAgdk=
+ dependencies:
+ core-util-is "~1.0.0"
+ inherits "~2.0.1"
+ isarray "0.0.1"
+ string_decoder "~0.10.x"
+
+readable-stream@^2.0.2, readable-stream@^2.2.2:
+ version "2.3.7"
+ resolved "https://registry.yarnpkg.com/readable-stream/-/readable-stream-2.3.7.tgz#1eca1cf711aef814c04f62252a36a62f6cb23b57"
+ integrity sha512-Ebho8K4jIbHAxnuxi7o42OrZgF/ZTNcsZj6nRKyUmkhLFq8CHItp/fy6hQZuZmP/n3yZ9VBUbp4zz/mX8hmYPw==
+ dependencies:
+ core-util-is "~1.0.0"
+ inherits "~2.0.3"
+ isarray "~1.0.0"
+ process-nextick-args "~2.0.0"
+ safe-buffer "~5.1.1"
+ string_decoder "~1.1.1"
+ util-deprecate "~1.0.1"
+
+readdirp@^2.2.1:
+ version "2.2.1"
+ resolved "https://registry.yarnpkg.com/readdirp/-/readdirp-2.2.1.tgz#0e87622a3325aa33e892285caf8b4e846529a525"
+ integrity sha512-1JU/8q+VgFZyxwrJ+SVIOsh+KywWGpds3NTqikiKpDMZWScmAYyKIgqkO+ARvNWJfXeXR1zxz7aHF4u4CyH6vQ==
+ dependencies:
+ graceful-fs "^4.1.11"
+ micromatch "^3.1.10"
+ readable-stream "^2.0.2"
+
+regex-not@^1.0.0, regex-not@^1.0.2:
+ version "1.0.2"
+ resolved "https://registry.yarnpkg.com/regex-not/-/regex-not-1.0.2.tgz#1f4ece27e00b0b65e0247a6810e6a85d83a5752c"
+ integrity sha512-J6SDjUgDxQj5NusnOtdFxDwN/+HWykR8GELwctJ7mdqhcyy1xEc4SRFHUXvxTp661YaVKAjfRLZ9cCqS6tn32A==
+ dependencies:
+ extend-shallow "^3.0.2"
+ safe-regex "^1.1.0"
+
+ version "3.3.2"
+ resolved "https://registry.yarnpkg.com/registry-auth-token/-/registry-auth-token-3.3.2.tgz#851fd49038eecb586911115af845260eec983f20"
+ integrity sha512-JL39c60XlzCVgNrO+qq68FoNb56w/m7JYvGR2jT5iR1xBrUA3Mfx5Twk5rqTThPmQKMWydGmq8oFtDlxfrmxnQ==
+ dependencies:
+ rc "^1.1.6"
+ safe-buffer "^5.0.1"
+
+registry-auth-token@^3.0.1:
+ version "3.4.0"
+ resolved "https://registry.yarnpkg.com/registry-auth-token/-/registry-auth-token-3.4.0.tgz#d7446815433f5d5ed6431cd5dca21048f66b397e"
+ integrity sha512-4LM6Fw8eBQdwMYcES4yTnn2TqIasbXuwDx3um+QRs7S55aMKCBKBxvPXl2RiUjHwuJLTyYfxSpmfSAjQpcuP+A==
+ dependencies:
+ rc "^1.1.6"
+ safe-buffer "^5.0.1"
+
[email protected], registry-url@^3.0.3:
+ version "3.1.0"
+ resolved "https://registry.yarnpkg.com/registry-url/-/registry-url-3.1.0.tgz#3d4ef870f73dde1d77f0cf9a381432444e174942"
+ integrity sha1-PU74cPc93h138M+aOBQyRE4XSUI=
+ dependencies:
+ rc "^1.0.1"
+
+remove-trailing-separator@^1.0.1:
+ version "1.1.0"
+ resolved "https://registry.yarnpkg.com/remove-trailing-separator/-/remove-trailing-separator-1.1.0.tgz#c24bce2a283adad5bc3f58e0d48249b92379d8ef"
+ integrity sha1-wkvOKig62tW8P1jg1IJJuSN52O8=
+
+repeat-element@^1.1.2:
+ version "1.1.3"
+ resolved "https://registry.yarnpkg.com/repeat-element/-/repeat-element-1.1.3.tgz#782e0d825c0c5a3bb39731f84efee6b742e6b1ce"
+ integrity sha512-ahGq0ZnV5m5XtZLMb+vP76kcAM5nkLqk0lpqAuojSKGgQtn4eRi4ZZGm2olo2zKFH+sMsWaqOCW1dqAnOru72g==
+
+repeat-string@^1.6.1:
+ version "1.6.1"
+ resolved "https://registry.yarnpkg.com/repeat-string/-/repeat-string-1.6.1.tgz#8dcae470e1c88abc2d600fff4a776286da75e637"
+ integrity sha1-jcrkcOHIirwtYA//Sndihtp15jc=
+
+resolve-url@^0.2.1:
+ version "0.2.1"
+ resolved "https://registry.yarnpkg.com/resolve-url/-/resolve-url-0.2.1.tgz#2c637fe77c893afd2a663fe21aa9080068e2052a"
+ integrity sha1-LGN/53yJOv0qZj/iGqkIAGjiBSo=
+
+ret@~0.1.10:
+ version "0.1.15"
+ resolved "https://registry.yarnpkg.com/ret/-/ret-0.1.15.tgz#b8a4825d5bdb1fc3f6f53c2bc33f81388681c7bc"
+ integrity sha512-TTlYpa+OL+vMMNG24xSlQGEJ3B/RzEfUlLct7b5G/ytav+wPrplCpVMFuwzXbkecJrb6IYo1iFb0S9v37754mg==
+
[email protected], safe-buffer@~5.1.0, safe-buffer@~5.1.1:
+ version "5.1.2"
+ resolved "https://registry.yarnpkg.com/safe-buffer/-/safe-buffer-5.1.2.tgz#991ec69d296e0313747d59bdfd2b745c35f8828d"
+ integrity sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==
+
+safe-buffer@^5.0.1:
+ version "5.2.1"
+ resolved "https://registry.yarnpkg.com/safe-buffer/-/safe-buffer-5.2.1.tgz#1eaf9fa9bdb1fdd4ec75f58f9cdb4e6b7827eec6"
+ integrity sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==
+
+safe-regex@^1.1.0:
+ version "1.1.0"
+ resolved "https://registry.yarnpkg.com/safe-regex/-/safe-regex-1.1.0.tgz#40a3669f3b077d1e943d44629e157dd48023bf2e"
+ integrity sha1-QKNmnzsHfR6UPURinhV91IAjvy4=
+ dependencies:
+ ret "~0.1.10"
+
+"safer-buffer@>= 2.1.2 < 3":
+ version "2.1.2"
+ resolved "https://registry.yarnpkg.com/safer-buffer/-/safer-buffer-2.1.2.tgz#44fa161b0187b9549dd84bb91802f9bd8385cd6a"
+ integrity sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==
+
+semver-diff@^2.0.0:
+ version "2.1.0"
+ resolved "https://registry.yarnpkg.com/semver-diff/-/semver-diff-2.1.0.tgz#4bbb8437c8d37e4b0cf1a68fd726ec6d645d6d36"
+ integrity sha1-S7uEN8jTfksM8aaP1ybsbWRdbTY=
+ dependencies:
+ semver "^5.0.3"
+
+semver@^5.0.3, semver@^5.1.0, semver@^5.7.1:
+ version "5.7.1"
+ resolved "https://registry.yarnpkg.com/semver/-/semver-5.7.1.tgz#a954f931aeba508d307bbf069eff0c01c96116f7"
+ integrity sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ==
+
+ version "0.17.1"
+ resolved "https://registry.yarnpkg.com/send/-/send-0.17.1.tgz#c1d8b059f7900f7466dd4938bdc44e11ddb376c8"
+ integrity sha512-BsVKsiGcQMFwT8UxypobUKyv7irCNRHk1T0G680vk88yf6LBByGcZJOTJCrTP2xVN6yI+XjPJcNuE3V4fT9sAg==
+ dependencies:
+ debug "2.6.9"
+ depd "~1.1.2"
+ destroy "~1.0.4"
+ encodeurl "~1.0.2"
+ escape-html "~1.0.3"
+ etag "~1.8.1"
+ fresh "0.5.2"
+ http-errors "~1.7.2"
+ mime "1.6.0"
+ ms "2.1.1"
+ on-finished "~2.3.0"
+ range-parser "~1.2.1"
+ statuses "~1.5.0"
+
+ version "6.1.3"
+ resolved "https://registry.yarnpkg.com/serve-handler/-/serve-handler-6.1.3.tgz#1bf8c5ae138712af55c758477533b9117f6435e8"
+ integrity sha512-FosMqFBNrLyeiIDvP1zgO6YoTzFYHxLDEIavhlmQ+knB2Z7l1t+kGLHkZIDN7UVWqQAmKI3D20A6F6jo3nDd4w==
+ dependencies:
+ bytes "3.0.0"
+ content-disposition "0.5.2"
+ fast-url-parser "1.1.3"
+ mime-types "2.1.18"
+ minimatch "3.0.4"
+ path-is-inside "1.0.2"
+ path-to-regexp "2.2.1"
+ range-parser "1.2.0"
+
+ version "1.14.1"
+ resolved "https://registry.yarnpkg.com/serve-static/-/serve-static-1.14.1.tgz#666e636dc4f010f7ef29970a88a674320898b2f9"
+ integrity sha512-JMrvUwE54emCYWlTI+hGrGv5I8dEwmco/00EvkzIIsR7MqrHonbD9pO2MOfFnpFntl7ecpZs+3mW+XbQZu9QCg==
+ dependencies:
+ encodeurl "~1.0.2"
+ escape-html "~1.0.3"
+ parseurl "~1.3.3"
+ send "0.17.1"
+
+serve@^11.0.0:
+ version "11.3.2"
+ resolved "https://registry.yarnpkg.com/serve/-/serve-11.3.2.tgz#b905e980616feecd170e51c8f979a7b2374098f5"
+ integrity sha512-yKWQfI3xbj/f7X1lTBg91fXBP0FqjJ4TEi+ilES5yzH0iKJpN5LjNb1YzIfQg9Rqn4ECUS2SOf2+Kmepogoa5w==
+ dependencies:
+ "@zeit/schemas" "2.6.0"
+ ajv "6.5.3"
+ arg "2.0.0"
+ boxen "1.3.0"
+ chalk "2.4.1"
+ clipboardy "1.2.3"
+ compression "1.7.3"
+ serve-handler "6.1.3"
+ update-check "1.5.2"
+
+set-value@^2.0.0, set-value@^2.0.1:
+ version "2.0.1"
+ resolved "https://registry.yarnpkg.com/set-value/-/set-value-2.0.1.tgz#a18d40530e6f07de4228c7defe4227af8cad005b"
+ integrity sha512-JxHc1weCN68wRY0fhCoXpyK55m/XPHafOmK4UWD7m2CI14GMcFypt4w/0+NV5f/ZMby2F6S2wwA7fgynh9gWSw==
+ dependencies:
+ extend-shallow "^2.0.1"
+ is-extendable "^0.1.1"
+ is-plain-object "^2.0.3"
+ split-string "^3.0.1"
+
+ version "1.1.1"
+ resolved "https://registry.yarnpkg.com/setprototypeof/-/setprototypeof-1.1.1.tgz#7e95acb24aa92f5885e0abef5ba131330d4ae683"
+ integrity sha512-JvdAWfbXeIGaZ9cILp38HntZSFSo3mWg6xGcJJsd+d4aRMOqauag1C63dJfDw7OaMYwEbHMOxEZ1lqVRYP2OAw==
+
+shebang-command@^1.2.0:
+ version "1.2.0"
+ resolved "https://registry.yarnpkg.com/shebang-command/-/shebang-command-1.2.0.tgz#44aac65b695b03398968c39f363fee5deafdf1ea"
+ integrity sha1-RKrGW2lbAzmJaMOfNj/uXer98eo=
+ dependencies:
+ shebang-regex "^1.0.0"
+
+shebang-regex@^1.0.0:
+ version "1.0.0"
+ resolved "https://registry.yarnpkg.com/shebang-regex/-/shebang-regex-1.0.0.tgz#da42f49740c0b42db2ca9728571cb190c98efea3"
+ integrity sha1-2kL0l0DAtC2yypcoVxyxkMmO/qM=
+
+signal-exit@^3.0.0, signal-exit@^3.0.2:
+ version "3.0.3"
+ resolved "https://registry.yarnpkg.com/signal-exit/-/signal-exit-3.0.3.tgz#a1410c2edd8f077b08b4e253c8eacfcaf057461c"
+ integrity sha512-VUJ49FC8U1OxwZLxIbTTrDvLnf/6TDgxZcK8wxR8zs13xpx7xbG60ndBlhNrFi2EMuFRoeDoJO7wthSLq42EjA==
+
+snapdragon-node@^2.0.1:
+ version "2.1.1"
+ resolved "https://registry.yarnpkg.com/snapdragon-node/-/snapdragon-node-2.1.1.tgz#6c175f86ff14bdb0724563e8f3c1b021a286853b"
+ integrity sha512-O27l4xaMYt/RSQ5TR3vpWCAB5Kb/czIcqUFOM/C4fYcLnbZUc1PkjTAMjof2pBWaSTwOUd6qUHcFGVGj7aIwnw==
+ dependencies:
+ define-property "^1.0.0"
+ isobject "^3.0.0"
+ snapdragon-util "^3.0.1"
+
+snapdragon-util@^3.0.1:
+ version "3.0.1"
+ resolved "https://registry.yarnpkg.com/snapdragon-util/-/snapdragon-util-3.0.1.tgz#f956479486f2acd79700693f6f7b805e45ab56e2"
+ integrity sha512-mbKkMdQKsjX4BAL4bRYTj21edOf8cN7XHdYUJEe+Zn99hVEYcMvKPct1IqNe7+AZPirn8BCDOQBHQZknqmKlZQ==
+ dependencies:
+ kind-of "^3.2.0"
+
+snapdragon@^0.8.1:
+ version "0.8.2"
+ resolved "https://registry.yarnpkg.com/snapdragon/-/snapdragon-0.8.2.tgz#64922e7c565b0e14204ba1aa7d6964278d25182d"
+ integrity sha512-FtyOnWN/wCHTVXOMwvSv26d+ko5vWlIDD6zoUJ7LW8vh+ZBC8QdljveRP+crNrtBwioEUWy/4dMtbBjA4ioNlg==
+ dependencies:
+ base "^0.11.1"
+ debug "^2.2.0"
+ define-property "^0.2.5"
+ extend-shallow "^2.0.1"
+ map-cache "^0.2.2"
+ source-map "^0.5.6"
+ source-map-resolve "^0.5.0"
+ use "^3.1.0"
+
+source-map-resolve@^0.5.0:
+ version "0.5.3"
+ resolved "https://registry.yarnpkg.com/source-map-resolve/-/source-map-resolve-0.5.3.tgz#190866bece7553e1f8f267a2ee82c606b5509a1a"
+ integrity sha512-Htz+RnsXWk5+P2slx5Jh3Q66vhQj1Cllm0zvnaY98+NFx+Dv2CF/f5O/t8x+KaNdrdIAsruNzoh/KpialbqAnw==
+ dependencies:
+ atob "^2.1.2"
+ decode-uri-component "^0.2.0"
+ resolve-url "^0.2.1"
+ source-map-url "^0.4.0"
+ urix "^0.1.0"
+
+source-map-url@^0.4.0:
+ version "0.4.0"
+ resolved "https://registry.yarnpkg.com/source-map-url/-/source-map-url-0.4.0.tgz#3e935d7ddd73631b97659956d55128e87b5084a3"
+ integrity sha1-PpNdfd1zYxuXZZlW1VEo6HtQhKM=
+
+source-map@^0.5.6:
+ version "0.5.7"
+ resolved "https://registry.yarnpkg.com/source-map/-/source-map-0.5.7.tgz#8a039d2d1021d22d1ea14c80d8ea468ba2ef3fcc"
+ integrity sha1-igOdLRAh0i0eoUyA2OpGi6LvP8w=
+
+split-string@^3.0.1, split-string@^3.0.2:
+ version "3.1.0"
+ resolved "https://registry.yarnpkg.com/split-string/-/split-string-3.1.0.tgz#7cb09dda3a86585705c64b39a6466038682e8fe2"
+ integrity sha512-NzNVhJDYpwceVVii8/Hu6DKfD2G+NrQHlS/V/qgv763EYudVwEcMQNxd2lh+0VrUByXN/oJkl5grOhYWvQUYiw==
+ dependencies:
+ extend-shallow "^3.0.0"
+
+sprintf-js@~1.0.2:
+ version "1.0.3"
+ resolved "https://registry.yarnpkg.com/sprintf-js/-/sprintf-js-1.0.3.tgz#04e6926f662895354f3dd015203633b857297e2c"
+ integrity sha1-BOaSb2YolTVPPdAVIDYzuFcpfiw=
+
+static-extend@^0.1.1:
+ version "0.1.2"
+ resolved "https://registry.yarnpkg.com/static-extend/-/static-extend-0.1.2.tgz#60809c39cbff55337226fd5e0b520f341f1fb5c6"
+ integrity sha1-YICcOcv/VTNyJv1eC1IPNB8ftcY=
+ dependencies:
+ define-property "^0.2.5"
+ object-copy "^0.1.0"
+
+"statuses@>= 1.5.0 < 2", statuses@~1.5.0:
+ version "1.5.0"
+ resolved "https://registry.yarnpkg.com/statuses/-/statuses-1.5.0.tgz#161c7dac177659fd9811f43771fa99381478628c"
+ integrity sha1-Fhx9rBd2Wf2YEfQ3cfqZOBR4Yow=
+
+ version "0.1.2"
+ resolved "https://registry.yarnpkg.com/streamsearch/-/streamsearch-0.1.2.tgz#808b9d0e56fc273d809ba57338e929919a1a9f1a"
+ integrity sha1-gIudDlb8Jz2Am6VzOOkpkZoanxo=
+
+strftime@^0.10.0:
+ version "0.10.0"
+ resolved "https://registry.yarnpkg.com/strftime/-/strftime-0.10.0.tgz#b3f0fa419295202a5a289f6d6be9f4909a617193"
+ integrity sha1-s/D6QZKVICpaKJ9ta+n0kJphcZM=
+
+string-width@^2.0.0, string-width@^2.1.1:
+ version "2.1.1"
+ resolved "https://registry.yarnpkg.com/string-width/-/string-width-2.1.1.tgz#ab93f27a8dc13d28cac815c462143a6d9012ae9e"
+ integrity sha512-nOqH59deCq9SRHlxq1Aw85Jnt4w6KvLKqWVik6oA9ZklXLNIOlqg4F2yrT1MVaTjAqvVwdfeZ7w7aCvJD7ugkw==
+ dependencies:
+ is-fullwidth-code-point "^2.0.0"
+ strip-ansi "^4.0.0"
+
+string_decoder@~0.10.x:
+ version "0.10.31"
+ resolved "https://registry.yarnpkg.com/string_decoder/-/string_decoder-0.10.31.tgz#62e203bc41766c6c28c9fc84301dab1c5310fa94"
+ integrity sha1-YuIDvEF2bGwoyfyEMB2rHFMQ+pQ=
+
+string_decoder@~1.1.1:
+ version "1.1.1"
+ resolved "https://registry.yarnpkg.com/string_decoder/-/string_decoder-1.1.1.tgz#9cf1611ba62685d7030ae9e4ba34149c3af03fc8"
+ integrity sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==
+ dependencies:
+ safe-buffer "~5.1.0"
+
+strip-ansi@^4.0.0:
+ version "4.0.0"
+ resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-4.0.0.tgz#a8479022eb1ac368a871389b635262c505ee368f"
+ integrity sha1-qEeQIusaw2iocTibY1JixQXuNo8=
+ dependencies:
+ ansi-regex "^3.0.0"
+
+strip-eof@^1.0.0:
+ version "1.0.0"
+ resolved "https://registry.yarnpkg.com/strip-eof/-/strip-eof-1.0.0.tgz#bb43ff5598a6eb05d89b59fcd129c983313606bf"
+ integrity sha1-u0P/VZim6wXYm1n80SnJgzE2Br8=
+
+strip-json-comments@~2.0.1:
+ version "2.0.1"
+ resolved "https://registry.yarnpkg.com/strip-json-comments/-/strip-json-comments-2.0.1.tgz#3c531942e908c2697c0ec344858c286c7ca0a60a"
+ integrity sha1-PFMZQukIwml8DsNEhYwobHygpgo=
+
+supports-color@^5.3.0, supports-color@^5.5.0:
+ version "5.5.0"
+ resolved "https://registry.yarnpkg.com/supports-color/-/supports-color-5.5.0.tgz#e2e69a44ac8772f78a1ec0b35b689df6530efc8f"
+ integrity sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==
+ dependencies:
+ has-flag "^3.0.0"
+
+term-size@^1.2.0:
+ version "1.2.0"
+ resolved "https://registry.yarnpkg.com/term-size/-/term-size-1.2.0.tgz#458b83887f288fc56d6fffbfad262e26638efa69"
+ integrity sha1-RYuDiH8oj8Vtb/+/rSYuJmOO+mk=
+ dependencies:
+ execa "^0.7.0"
+
+timed-out@^4.0.0:
+ version "4.0.1"
+ resolved "https://registry.yarnpkg.com/timed-out/-/timed-out-4.0.1.tgz#f32eacac5a175bea25d7fab565ab3ed8741ef56f"
+ integrity sha1-8y6srFoXW+ol1/q1Zas+2HQe9W8=
+
+to-object-path@^0.3.0:
+ version "0.3.0"
+ resolved "https://registry.yarnpkg.com/to-object-path/-/to-object-path-0.3.0.tgz#297588b7b0e7e0ac08e04e672f85c1f4999e17af"
+ integrity sha1-KXWIt7Dn4KwI4E5nL4XB9JmeF68=
+ dependencies:
+ kind-of "^3.0.2"
+
+to-regex-range@^2.1.0:
+ version "2.1.1"
+ resolved "https://registry.yarnpkg.com/to-regex-range/-/to-regex-range-2.1.1.tgz#7c80c17b9dfebe599e27367e0d4dd5590141db38"
+ integrity sha1-fIDBe53+vlmeJzZ+DU3VWQFB2zg=
+ dependencies:
+ is-number "^3.0.0"
+ repeat-string "^1.6.1"
+
+to-regex@^3.0.1, to-regex@^3.0.2:
+ version "3.0.2"
+ resolved "https://registry.yarnpkg.com/to-regex/-/to-regex-3.0.2.tgz#13cfdd9b336552f30b51f33a8ae1b42a7a7599ce"
+ integrity sha512-FWtleNAtZ/Ki2qtqej2CXTOayOH9bHDQF+Q48VpWyDXjbYxA4Yz8iDB31zXOBUlOHHKidDbqGVrTUvQMPmBGBw==
+ dependencies:
+ define-property "^2.0.2"
+ extend-shallow "^3.0.2"
+ regex-not "^1.0.2"
+ safe-regex "^1.1.0"
+
+ version "1.0.0"
+ resolved "https://registry.yarnpkg.com/toidentifier/-/toidentifier-1.0.0.tgz#7e1be3470f1e77948bc43d94a3c8f4d7752ba553"
+ integrity sha512-yaOH/Pk/VEhBWWTlhI+qXxDFXlejDGcQipMlyxda9nthulaxLZUNcUqFxokp0vcYnvteJln5FNQDRrxj3YcbVw==
+
+touch@^3.1.0:
+ version "3.1.0"
+ resolved "https://registry.yarnpkg.com/touch/-/touch-3.1.0.tgz#fe365f5f75ec9ed4e56825e0bb76d24ab74af83b"
+ integrity sha512-WBx8Uy5TLtOSRtIq+M03/sKDrXCLHxwDcquSP2c43Le03/9serjQBIztjRz6FkJez9D/hleyAXTBGLwwZUw9lA==
+ dependencies:
+ nopt "~1.0.10"
+
+type-is@^1.6.4, type-is@~1.6.17, type-is@~1.6.18:
+ version "1.6.18"
+ resolved "https://registry.yarnpkg.com/type-is/-/type-is-1.6.18.tgz#4e552cd05df09467dcbc4ef739de89f2cf37c131"
+ integrity sha512-TkRKr9sUTxEH8MdfuCSP7VizJyzRNMjj2J2do2Jr3Kym598JVdEksuzPQCnlFPW4ky9Q+iA+ma9BGm06XQBy8g==
+ dependencies:
+ media-typer "0.3.0"
+ mime-types "~2.1.24"
+
+typedarray@^0.0.6:
+ version "0.0.6"
+ resolved "https://registry.yarnpkg.com/typedarray/-/typedarray-0.0.6.tgz#867ac74e3864187b1d3d47d996a78ec5c8830777"
+ integrity sha1-hnrHTjhkGHsdPUfZlqeOxciDB3c=
+
+undefsafe@^2.0.2:
+ version "2.0.3"
+ resolved "https://registry.yarnpkg.com/undefsafe/-/undefsafe-2.0.3.tgz#6b166e7094ad46313b2202da7ecc2cd7cc6e7aae"
+ integrity sha512-nrXZwwXrD/T/JXeygJqdCO6NZZ1L66HrxM/Z7mIq2oPanoN0F1nLx3lwJMu6AwJY69hdixaFQOuoYsMjE5/C2A==
+ dependencies:
+ debug "^2.2.0"
+
+union-value@^1.0.0:
+ version "1.0.1"
+ resolved "https://registry.yarnpkg.com/union-value/-/union-value-1.0.1.tgz#0b6fe7b835aecda61c6ea4d4f02c14221e109847"
+ integrity sha512-tJfXmxMeWYnczCVs7XAEvIV7ieppALdyepWMkHkwciRpZraG/xwT+s2JN8+pr1+8jCRf80FFzvr+MpQeeoF4Xg==
+ dependencies:
+ arr-union "^3.1.0"
+ get-value "^2.0.6"
+ is-extendable "^0.1.1"
+ set-value "^2.0.1"
+
+unique-string@^1.0.0:
+ version "1.0.0"
+ resolved "https://registry.yarnpkg.com/unique-string/-/unique-string-1.0.0.tgz#9e1057cca851abb93398f8b33ae187b99caec11a"
+ integrity sha1-nhBXzKhRq7kzmPizOuGHuZyuwRo=
+ dependencies:
+ crypto-random-string "^1.0.0"
+
[email protected], unpipe@~1.0.0:
+ version "1.0.0"
+ resolved "https://registry.yarnpkg.com/unpipe/-/unpipe-1.0.0.tgz#b2bf4ee8514aae6165b4817829d21b2ef49904ec"
+ integrity sha1-sr9O6FFKrmFltIF4KdIbLvSZBOw=
+
+unset-value@^1.0.0:
+ version "1.0.0"
+ resolved "https://registry.yarnpkg.com/unset-value/-/unset-value-1.0.0.tgz#8376873f7d2335179ffb1e6fc3a8ed0dfc8ab559"
+ integrity sha1-g3aHP30jNRef+x5vw6jtDfyKtVk=
+ dependencies:
+ has-value "^0.3.1"
+ isobject "^3.0.0"
+
+unzip-response@^2.0.1:
+ version "2.0.1"
+ resolved "https://registry.yarnpkg.com/unzip-response/-/unzip-response-2.0.1.tgz#d2f0f737d16b0615e72a6935ed04214572d56f97"
+ integrity sha1-0vD3N9FrBhXnKmk17QQhRXLVb5c=
+
+upath@^1.1.1:
+ version "1.2.0"
+ resolved "https://registry.yarnpkg.com/upath/-/upath-1.2.0.tgz#8f66dbcd55a883acdae4408af8b035a5044c1894"
+ integrity sha512-aZwGpamFO61g3OlfT7OQCHqhGnW43ieH9WZeP7QxN/G/jS4jfqUkZxoryvJgVPEcrl5NL/ggHsSmLMHuH64Lhg==
+
+ version "1.5.2"
+ resolved "https://registry.yarnpkg.com/update-check/-/update-check-1.5.2.tgz#2fe09f725c543440b3d7dabe8971f2d5caaedc28"
+ integrity sha512-1TrmYLuLj/5ZovwUS7fFd1jMH3NnFDN1y1A8dboedIDt7zs/zJMo6TwwlhYKkSeEwzleeiSBV5/3c9ufAQWDaQ==
+ dependencies:
+ registry-auth-token "3.3.2"
+ registry-url "3.1.0"
+
+update-notifier@^2.5.0:
+ version "2.5.0"
+ resolved "https://registry.yarnpkg.com/update-notifier/-/update-notifier-2.5.0.tgz#d0744593e13f161e406acb1d9408b72cad08aff6"
+ integrity sha512-gwMdhgJHGuj/+wHJJs9e6PcCszpxR1b236igrOkUofGhqJuG+amlIKwApH1IW1WWl7ovZxsX49lMBWLxSdm5Dw==
+ dependencies:
+ boxen "^1.2.1"
+ chalk "^2.0.1"
+ configstore "^3.0.0"
+ import-lazy "^2.1.0"
+ is-ci "^1.0.10"
+ is-installed-globally "^0.1.0"
+ is-npm "^1.0.0"
+ latest-version "^3.0.0"
+ semver-diff "^2.0.0"
+ xdg-basedir "^3.0.0"
+
+uri-js@^4.2.2:
+ version "4.4.0"
+ resolved "https://registry.yarnpkg.com/uri-js/-/uri-js-4.4.0.tgz#aa714261de793e8a82347a7bcc9ce74e86f28602"
+ integrity sha512-B0yRTzYdUCCn9n+F4+Gh4yIDtMQcaJsmYBDsTSG8g/OejKBodLQ2IHfN3bM7jUsRXndopT7OIXWdYqc1fjmV6g==
+ dependencies:
+ punycode "^2.1.0"
+
+urix@^0.1.0:
+ version "0.1.0"
+ resolved "https://registry.yarnpkg.com/urix/-/urix-0.1.0.tgz#da937f7a62e21fec1fd18d49b35c2935067a6c72"
+ integrity sha1-2pN/emLiH+wf0Y1Js1wpNQZ6bHI=
+
+url-parse-lax@^1.0.0:
+ version "1.0.0"
+ resolved "https://registry.yarnpkg.com/url-parse-lax/-/url-parse-lax-1.0.0.tgz#7af8f303645e9bd79a272e7a14ac68bc0609da73"
+ integrity sha1-evjzA2Rem9eaJy56FKxovAYJ2nM=
+ dependencies:
+ prepend-http "^1.0.1"
+
+use@^3.1.0:
+ version "3.1.1"
+ resolved "https://registry.yarnpkg.com/use/-/use-3.1.1.tgz#d50c8cac79a19fbc20f2911f56eb973f4e10070f"
+ integrity sha512-cwESVXlO3url9YWlFW/TA9cshCEhtu7IKJ/p5soJ/gGpj7vbvFrAY/eIioQ6Dw23KjZhYgiIo8HOs1nQ2vr/oQ==
+
+util-deprecate@~1.0.1:
+ version "1.0.2"
+ resolved "https://registry.yarnpkg.com/util-deprecate/-/util-deprecate-1.0.2.tgz#450d4dc9fa70de732762fbd2d4a28981419a0ccf"
+ integrity sha1-RQ1Nyfpw3nMnYvvS1KKJgUGaDM8=
+
+ version "1.0.1"
+ resolved "https://registry.yarnpkg.com/utils-merge/-/utils-merge-1.0.1.tgz#9f95710f50a267947b2ccc124741c1028427e713"
+ integrity sha1-n5VxD1CiZ5R7LMwSR0HBAoQn5xM=
+
+vary@~1.1.2:
+ version "1.1.2"
+ resolved "https://registry.yarnpkg.com/vary/-/vary-1.1.2.tgz#2299f02c6ded30d4a5961b0b9f74524a18f634fc"
+ integrity sha1-IpnwLG3tMNSllhsLn3RSShj2NPw=
+
+which@^1.2.9:
+ version "1.3.1"
+ resolved "https://registry.yarnpkg.com/which/-/which-1.3.1.tgz#a45043d54f5805316da8d62f9f50918d3da70b0a"
+ integrity sha512-HxJdYWq1MTIQbJ3nw0cqssHoTNU267KlrDuGZ1WYlxDStUtKUhOaJmh112/TZmHxxUfuJqPXSOm7tDyas0OSIQ==
+ dependencies:
+ isexe "^2.0.0"
+
+widest-line@^2.0.0:
+ version "2.0.1"
+ resolved "https://registry.yarnpkg.com/widest-line/-/widest-line-2.0.1.tgz#7438764730ec7ef4381ce4df82fb98a53142a3fc"
+ integrity sha512-Ba5m9/Fa4Xt9eb2ELXt77JxVDV8w7qQrH0zS/TWSJdLyAwQjWoOzpzj5lwVftDz6n/EOu3tNACS84v509qwnJA==
+ dependencies:
+ string-width "^2.1.1"
+
+wrappy@1:
+ version "1.0.2"
+ resolved "https://registry.yarnpkg.com/wrappy/-/wrappy-1.0.2.tgz#b5243d8f3ec1aa35f1364605bc0d1036e30ab69f"
+ integrity sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8=
+
+write-file-atomic@^2.0.0:
+ version "2.4.3"
+ resolved "https://registry.yarnpkg.com/write-file-atomic/-/write-file-atomic-2.4.3.tgz#1fd2e9ae1df3e75b8d8c367443c692d4ca81f481"
+ integrity sha512-GaETH5wwsX+GcnzhPgKcKjJ6M2Cq3/iZp1WyY/X1CSqrW+jVNM9Y7D8EC2sM4ZG/V8wZlSniJnCKWPmBYAucRQ==
+ dependencies:
+ graceful-fs "^4.1.11"
+ imurmurhash "^0.1.4"
+ signal-exit "^3.0.2"
+
+xdg-basedir@^3.0.0:
+ version "3.0.0"
+ resolved "https://registry.yarnpkg.com/xdg-basedir/-/xdg-basedir-3.0.0.tgz#496b2cc109eca8dbacfe2dc72b603c17c5870ad4"
+ integrity sha1-SWsswQnsqNus/i3HK2A8F8WHCtQ=
+
+xtend@^4.0.0:
+ version "4.0.2"
+ resolved "https://registry.yarnpkg.com/xtend/-/xtend-4.0.2.tgz#bb72779f5fa465186b1f438f674fa347fdb5db54"
+ integrity sha512-LKYU1iAXJXUgAXn9URjiu+MWhyUXHsvfp7mcuYm9dSUKK0/CjtrUwFAxD82/mCWbtLsGjFIad0wIsod4zrTAEQ==
+
+yallist@^2.1.2:
+ version "2.1.2"
+ resolved "https://registry.yarnpkg.com/yallist/-/yallist-2.1.2.tgz#1c11f9218f076089a47dd512f93c6699a6a81d52"
+ integrity sha1-HBH5IY8HYImkfdUS+TxmmaaoHVI=