Browse Source

Update CV and use grapefruit for output

Craig Fletcher 6 years ago
parent
commit
f0ef1d12cb

+ 3 - 0
.gitignore

@@ -0,0 +1,3 @@
+node_modules
+output
+last-build.log

+ 112 - 0
.npm-scripts/build-pipeline.js

@@ -0,0 +1,112 @@
+const path = require("path");
+
+function getNiceNameFromFilename(filename) {
+  return (filename.charAt(0).toUpperCase() + filename.slice(1)).replace(
+    /-/g,
+    " "
+  );
+}
+
+function meta(config, item, meta) {
+  const outputDir = path.join(meta.dir.replace(config.baseDir, ""));
+  return {
+    ...item,
+    name: meta.name,
+    niceName: getNiceNameFromFilename(meta.name),
+    outputPath: `${path.join(outputDir, meta.name)}`,
+    outputExtension: ".html"
+  };
+}
+
+function addBlurb(config, item, meta) {
+  const introStart = item.content.indexOf("<p>") + 3;
+  const introEnd = item.content.indexOf("</p>");
+  let cutoff;
+  if (introEnd - introStart > config.cutoffLength) {
+    cutoff = item.content.indexOf(".", introStart + config.cutoffLength) + 1;
+  } else {
+    cutoff = introEnd;
+  }
+  const blurb = item.content.substring(introStart, cutoff);
+  return {
+    ...item,
+    blurb: blurb
+  };
+}
+
+module.exports = {
+  initialState: [
+    {
+      path: "./cv.md",
+      tags: ["markdown", "content"]
+    },
+    {
+      path: "./styles.css",
+      tags: ["styles"]
+    },
+    {
+      path: "./template.handlebars",
+      tags: ["template"]
+    }
+  ],
+  steps: [
+    [
+      {
+        func: "readInFile",
+        selector: ({ selectMany }) => selectMany(item => Boolean(item.path))
+      }
+    ],
+    [
+      {
+        func: "decorateFileObject",
+        selector: ({ selectMany }) => selectMany(item => true),
+        config: {
+          decorators: [meta]
+        }
+      }
+    ],
+    [
+      {
+        func: "markdownToHtml",
+        selector: ({ selectByTag }) => selectByTag("markdown")
+      },
+      {
+        func: "compileTemplates",
+        selector: ({ selectByTag }) => selectByTag("template")
+      },
+      {
+        func: "copy",
+        selector: ({ selectByTag }) => selectByTag("styles")
+      }
+    ],
+    [
+      {
+        func: "renderTemplate",
+        selector: ({ selectOne }) =>
+          selectOne(
+            item => item.tags.includes("markdown") && item.name === "cv"
+          ),
+        getConfig: ({ selectByTag, selectOne }) => ({
+          meta: {
+            styles: selectOne(item => item.name === "styles")
+          },
+          template: selectOne(
+            item => item.tags.includes("template") && item.name === "template"
+          )
+        })
+      }
+    ],
+    [
+      {
+        func: "writeOutFile",
+        selector: ({ selectMany }) => selectMany(item => true),
+        config: { outputDir: "./output" }
+      },
+      {
+        func: "writeOutPDF",
+        selector: ({ selectMany }) => selectMany(item => true),
+        config: { outputDir: "./output" }
+      }
+    ]
+  ]
+};

+ 53 - 0
.npm-scripts/build.js

@@ -0,0 +1,53 @@
+const Grapefruit = require("grapefruit");
+const careless = require("careless-fs");
+const buildPipeline = require("./build-pipeline");
+const history = new Grapefruit.History();
+
+const runner = new Grapefruit({
+  emitter: history.push,
+  funcs: {
+    decorateFileObject: require("./funcs/decorateFileObject.js"),
+    readInFile: require("./funcs/readInFile.js"),
+    writeOutFile: require("./funcs/writeOutFile.js"),
+    writeOutPDF: require("./funcs/writeOutPDF.js"),
+    compileTemplates: require("./funcs/compileTemplates.js"),
+    renderTemplate: require("./funcs/renderTemplate.js"),
+    markdownToHtml: require("./funcs/markdownToHtml.js"),
+    copy: require("./funcs/copy.js")
+  }
+});
+
+const pipeline = runner.runPipeline(buildPipeline);
+pipeline
+  .then(function(res) {
+    careless
+      .write({
+        path: "last-build.log",
+        content: JSON.stringify(history.get(), null, 2)
+      })
+      .then(file => {
+        console.log(`History written to ${file.path}`);
+      })
+      .catch(e => {
+        console.log(
+          `History could not be written: ${JSON.stringify(e, null, 2)}`
+        );
+      });
+    console.log("Pipeline complete.");
+  })
+  .catch(function(err) {
+    careless
+      .write({
+        path: "last-build.log",
+        content: JSON.stringify(history.get(), null, 2)
+      })
+      .then(file => {
+        console.log(`History written to ${file.path}`);
+      })
+      .catch(e => {
+        console.log(
+          `History could not be written: ${JSON.stringify(e, null, 2)}`
+        );
+      });
+    console.log("Pipeline error:", JSON.stringify(err.message, null, 2));
+  });

+ 11 - 0
.npm-scripts/funcs/compileTemplates.js

@@ -0,0 +1,11 @@
+const Handlebars = require("handlebars");
+const handlebarsCompiler = Handlebars.compile;
+
+module.exports = function(config, item) {
+  return new Promise(function(resolve) {
+    resolve({
+      ...item,
+      template: handlebarsCompiler(item.content)
+    });
+  });
+};

+ 8 - 0
.npm-scripts/funcs/copy.js

@@ -0,0 +1,8 @@
+module.exports = function(config, item) {
+  return new Promise(function(resolve) {
+    resolve({
+      ...item
+    });
+  });
+};
+

+ 28 - 0
.npm-scripts/funcs/copyFile.js

@@ -0,0 +1,28 @@
+const fsN = require("fs");
+const path = require("path");
+const mkdirp = require("mkdirp");
+
+module.exports = function(input, options) {
+  return new Promise(function(resolve, reject) {
+    Promise.all(
+      input.currentJob.files.map(file => {
+        return new Promise(function(resolve, reject) {
+          const outputPath = path.join(options.outputDir, file.outputPath);
+          mkdirp(path.dirname(outputPath), function(err) {
+            if (err) {
+              reject(err);
+            }
+            fsN.copyFile(file.path, outputPath, function(err) {
+              if (err) {
+                reject(err);
+              }
+              resolve(file);
+            });
+          });
+        });
+      })
+    )
+      .then(res => resolve(input))
+      .catch(err => reject(err));
+  });
+};

+ 13 - 0
.npm-scripts/funcs/decorateFileObject.js

@@ -0,0 +1,13 @@
+const path = require("path");
+
+module.exports = function(config, item) {
+  return new Promise(function(resolve, reject) {
+    const meta = path.parse(item.path);
+    resolve(
+      Object.assign(
+        item,
+        ...config.decorators.map(decorator => decorator(config, item, meta))
+      )
+    );
+  });
+};

+ 25 - 0
.npm-scripts/funcs/listDirectory.js

@@ -0,0 +1,25 @@
+const fsN = require("fs");
+const path = require("path");
+
+module.exports = function(config, item) {
+  return new Promise(function(resolve, reject) {
+    fsN.readdir(config.directory, { withFileTypes: true }, function(
+      err,
+      dirListing
+    ) {
+      if (err) {
+        reject(err);
+      }
+      resolve(
+        dirListing.reduce((filenames, fileDirent) => {
+          return fileDirent.isFile()
+            ? filenames.concat({
+                ...item,
+                path: path.join(config.directory, fileDirent.name)
+              })
+            : filenames;
+        }, [])
+      );
+    });
+  });
+};

+ 10 - 0
.npm-scripts/funcs/markdownToHtml.js

@@ -0,0 +1,10 @@
+const markdown = require("markdown").markdown;
+
+module.exports = function(config, item) {
+  return new Promise(function(resolve) {
+    resolve({
+      ...item,
+      content: markdown.toHTML(item.content)
+    });
+  });
+};

+ 15 - 0
.npm-scripts/funcs/minifyHtml.js

@@ -0,0 +1,15 @@
+const minify = require("html-minifier").minify;
+
+module.exports = function(config, item) {
+  return new Promise(function(resolve, reject) {
+    resolve({
+      ...item,
+      content: minify(item.content, {
+        removeAttributeQuotes: true,
+        minifyCSS: true,
+        minifyJS: true,
+        collapseWhitespace: true
+      })
+    });
+  });
+};

+ 11 - 0
.npm-scripts/funcs/parseJson.js

@@ -0,0 +1,11 @@
+module.exports = function(input, options) {
+  return new Promise(function(resolve, reject) {
+    input.currentJob.files = input.currentJob.files.map(file => {
+      return {
+        ...file,
+        content: JSON.parse(file.content),
+      };
+    });
+    resolve(input);
+  });
+};

+ 16 - 0
.npm-scripts/funcs/readInFile.js

@@ -0,0 +1,16 @@
+const fs = require("careless-fs");
+
+module.exports = function(config, item) {
+  return new Promise(function(resolve, reject) {
+    fs.read(item.path)
+      .then(function(data) {
+        resolve({
+          ...item,
+          ...data
+        });
+      })
+      .catch(function(err) {
+        reject(err);
+      });
+  });
+};

+ 22 - 0
.npm-scripts/funcs/renderTemplate.js

@@ -0,0 +1,22 @@
+function toObject(arr) {
+  let rv = {};
+  let i;
+  for (i = 0; i < arr.length; ++i) rv[arr[i].name] = arr[i];
+  return rv;
+}
+module.exports = function(config, item) {
+  return new Promise(function(resolve, reject) {
+    if (!config.template || !config.template.template) {
+      reject({ message: "Template not found", item, config });
+    }
+    try {
+      const content = config.template.template({ item, meta: config.meta });
+      resolve({
+        ...item,
+        content
+      });
+    } catch (e) {
+      reject({ message: "Could not render template", funcError: e });
+    }
+  });
+};

+ 7 - 0
.npm-scripts/funcs/writeOutFile.js

@@ -0,0 +1,7 @@
+const fs = require("careless-fs");
+module.exports = function(config, item) {
+  return fs.write({
+    path: `${config.outputDir}/${item.outputPath}${item.outputExtension}`,
+    content: item.content
+  });
+};

+ 17 - 0
.npm-scripts/funcs/writeOutPDF.js

@@ -0,0 +1,17 @@
+const pdf = require("html-pdf");
+module.exports = function(config, item) {
+  return new Promise((resolve, reject) => {
+    const path = `${config.outputDir}/${item.outputPath}.pdf`;
+
+    pdf.create(item.content).toFile(path, function(err, res) {
+      if (err) {
+        reject(err);
+      } else {
+        resolve({
+          ...item,
+          path: res.filename
+        });
+      }
+    });
+  });
+};

+ 0 - 135
cv.html

@@ -1,135 +0,0 @@
-<!DOCTYPE html>
-<html>
-<head>
-<link rel="stylesheet" href="styles.css">
-<title></title>
-</head>
-<body>
-<section>
-<header>
-<h1>Hello, I'm Craig Fletcher.</h1>
-</header>
-<p>You can call me on 07480924132, email me at <a href=
-"mailto:hi@leakypixel.net">hi@leakypixel.net</a>, or check out what
-I'm getting on with at <a href=
-"https://www.leakypixel.net/">leakypixel.net</a>.</p>
-<p>I've been developing for the web for around 10 years and I'm
-still as eager as ever to push my skills and learn new things. I
-specialise in JavaScript - particularly React, but have worked with
-many languages, libraries and tools appropriate to the task at
-hand.</p>
-<h2>Experience</h2>
-<p>A short description of my last few roles and what they entailed.
-I'd be happy to discuss these further, but for now I'll keep it
-brief.</p>
-<h3>Erlang Solutions - JavaScript consultant (9 months)</h3>
-<p><em>2018 to 2019</em></p>
-<p>Worked closely with a worldwide distributed team to develop a
-deliveroo-like service for fuel and related product delivery. As
-the only JavaScript developer on the team, I built a react and
-apollo based web application to interact with an erlang graphql
-service that was being developed in tandem by other members of the
-team.</p>
-<h3>The Co-op Group - JavaScript consultant (6 months)</h3>
-<p><em>2018</em></p>
-<p>As part of the ventures team, built a new marketplace
-application using react for the frontend and python with flask for
-the backend. Worked in a team of 2 to get the project off the
-ground and into production. This project was more focused toward
-user research and constant iteration than technical challenges,
-something I really enjoyed.</p>
-<h3>Life's Great - JavaScript consultant (6 months)</h3>
-<p><em>2017 to 2018</em></p>
-<p>Upskilled team into react development and assisted with building
-a greenfield project:</p>
-<ul>
-<li>Upskilling team with limited to no prior knowledge of react
-through pairing, presentations and walkthroughs.</li>
-<li>Built a new application to gather information from customers
-through a conditional, staged questionnaire with complex
-dependencies.</li>
-<li>Various devops related tasks from scratch, such as docker
-scripts.</li>
-<li>Advising management on various technical decisions vital for
-the company.</li>
-</ul>
-<h3>Earlier</h3>
-<p><em>2017 - Tyres on the Drive - JavaScript consultant (12
-months)</em></p>
-<p><em>2016 - The Hut Group - JavaScript developer (12
-months)</em></p>
-<p><em>2015 - Bet365 - JavaScript developer (6 months)</em></p>
-<p><em>2013 to 2015 - On The Beach ltd. - Front end developer,
-Permanent</em></p>
-<h2>Skills</h2>
-<p>Some of the skills I've picked up along the way and tools I've
-used.</p>
-<h3>JavaScript</h3>
-<ul>
-<li>React, Redux, Apollo GraphQL</li>
-<li>NodeJS</li>
-<li>Document DBs (Mongo/Couchbase)</li>
-<li>Client-side templating (Handlebars/Mustache/jst)</li>
-<li>Module loading & dependency resolution</li>
-<li>Testing & automation (Jasmine/Karma/Istanbul/Selenium)</li>
-<li>Build systems (Grunt/Gulp/Webpack)</li>
-<li>WebSockets (Pusher/native)</li>
-<li>Modern ECMA script</li>
-</ul>
-<h3>HTML & CSS</h3>
-<ul>
-<li>Sass/SCSS/LESS</li>
-<li>Responsive design</li>
-<li>Modular design & web components</li>
-<li>Accessibility/Information hierarchy</li>
-<li>Progressive enhancement</li>
-</ul>
-<h3>Others</h3>
-<ul>
-<li>AWS and Digital ocean</li>
-<li>Continuous integration (Jenkins/Travis)</li>
-<li>Git (GitHub/CLI/GitHooks/Workflows)</li>
-<li>Docker & docker-compose</li>
-<li>Agile, Kanban & Scrum</li>
-<li>Project management tools (JIRA/Trello)</li>
-<li>Building appliances/discreet servers (Raspberry Pi/Intel Atom
-SoC)</li>
-<li>Analytics (Google/server stats/pingdom/custom built)</li>
-<li>Basic server administration (nginx/Apache/caddy)</li>
-<li>Versioning (npm/git tags/artifactory/docker images)</li>
-<li>Linux/UNIX-like OSes (Debian/Arch/FreeBSD)</li>
-<li>Bash (day to day environment & scripting)</li>
-<li>Python</li>
-</ul>
-<h3>Experience of</h3>
-<ul>
-<li>Java (Spring)</li>
-<li>Ruby (Rails)</li>
-<li>Golang (Iris)</li>
-<li>Cordova/PhoneGap</li>
-<li>Erlang (Phoenix)</li>
-</ul>
-<h2>I'm not boring</h2>
-<p>I have quite a few hobbies outside of my development work, and
-try to keep myself as active as I can - especially during the
-summer, when I can get outside.</p>
-<ul>
-<li>Rock climbing and bouldering are my big hobbies... though they
-don't look a lot different from the outside, there's quite a gap. I
-vary between the two, but practice indoors twice a week and get
-outside when I can.</li>
-<li>Somehow I ended up being a Scout leader, too. I was part of the
-organisation when I was younger, and after a couple of years break,
-I went back to help out. It has greatly helped me in maintaining
-leadership skills, and organising larger groups of people.</li>
-<li>I also spend quite a lot of my free time tinkering and working
-on personal projects, such as building raspberry pi based camera
-systems, monitors, or arcade systems.</li>
-<li>To keep up with the fast-paced world of development, I spend
-quite a lot of my free time reading, or listening to podcasts. Some
-of my favourites are 99 percent invisible, bad voltage and project
-bouldering.</li>
-</ul>
-</section>
-</body>
-</html>

+ 23 - 9
cv.md

@@ -8,7 +8,15 @@ I've been developing for the web for around 10 years and I'm still as eager as e
 A short description of my last few roles and what they entailed. I'd be happy to
 A short description of my last few roles and what they entailed. I'd be happy to
 discuss these further, but for now I'll keep it brief.
 discuss these further, but for now I'll keep it brief.
 
 
-### Erlang Solutions - JavaScript consultant (9 months)
+### AKQA - JavaScript consultant (3 months, fully remote)
+*2019*
+
+Worked on a high-profile project for a well-known international clothing brand,
+using React, Netlify, Next.js, AWS lambda, nodeJS and AWS dynamoDB. Working remotely as
+part of a small, internationally distributed team to deliver a time-critical
+project with many challenges and tight deadlines.
+
+### Erlang Solutions - JavaScript consultant (9 months, fully remote)
 *2018 to 2019*
 *2018 to 2019*
 
 
 Worked closely with a worldwide distributed team to develop a deliveroo-like
 Worked closely with a worldwide distributed team to develop a deliveroo-like
@@ -17,7 +25,7 @@ on the team, I built a react and apollo based web application to interact with
 an erlang graphql service that was being developed in tandem by other members 
 an erlang graphql service that was being developed in tandem by other members 
 of the team.
 of the team.
 
 
-### The Co-op Group - JavaScript consultant (6 months)
+### The Co-op Group - JavaScript consultant (6 months, part remote)
 *2018* 
 *2018* 
 
 
 As part of the ventures team, built a new marketplace application using react
 As part of the ventures team, built a new marketplace application using react
@@ -26,16 +34,20 @@ get the project off the ground and into production. This project was more
 focused toward user research and constant iteration than technical challenges,
 focused toward user research and constant iteration than technical challenges,
 something I really enjoyed.
 something I really enjoyed.
 
 
-### Life's Great - JavaScript consultant (6 months)
+### Life's Great - JavaScript consultant (6 months, part remote)
 *2017 to 2018* 
 *2017 to 2018* 
 
 
 Upskilled team into react development and assisted with building a greenfield
 Upskilled team into react development and assisted with building a greenfield
 project:
 project:
+
 * Upskilling team with limited to no prior knowledge of react through pairing,
 * Upskilling team with limited to no prior knowledge of react through pairing,
     presentations and walkthroughs.
     presentations and walkthroughs.
+
 * Built a new application to gather information from customers through a
 * Built a new application to gather information from customers through a
     conditional, staged questionnaire with complex dependencies.
     conditional, staged questionnaire with complex dependencies.
+
 * Various devops related tasks from scratch, such as docker scripts.
 * Various devops related tasks from scratch, such as docker scripts.
+
 * Advising management on various technical decisions vital for the company.
 * Advising management on various technical decisions vital for the company.
 
 
 ### Earlier
 ### Earlier
@@ -53,7 +65,7 @@ Some of the skills I've picked up along the way and tools I've used.
 ### JavaScript
 ### JavaScript
 * React, Redux, Apollo GraphQL
 * React, Redux, Apollo GraphQL
 * NodeJS
 * NodeJS
-* Document DBs (Mongo/Couchbase)
+* Document DBs (Mongo/Couchbase/DynamoDB)
 * Client-side templating (Handlebars/Mustache/jst)
 * Client-side templating (Handlebars/Mustache/jst)
 * Module loading & dependency resolution
 * Module loading & dependency resolution
 * Testing & automation (Jasmine/Karma/Istanbul/Selenium)
 * Testing & automation (Jasmine/Karma/Istanbul/Selenium)
@@ -73,19 +85,21 @@ Some of the skills I've picked up along the way and tools I've used.
 * Cross browser compatibility
 * Cross browser compatibility
 * HTML 4 & 5
 * HTML 4 & 5
 
 
-### Others
+### Devops related
 * AWS and Digital ocean
 * AWS and Digital ocean
 * Continuous integration (Jenkins/Travis)
 * Continuous integration (Jenkins/Travis)
+* Docker & docker-compose
+* Basic server administration (nginx/Apache/caddy)
+* Versioning (npm/git tags/artifactory/docker images)
+* Linux/UNIX-like OSes (Debian/Arch/FreeBSD)
+
+### Others
 * UX and data-driven design
 * UX and data-driven design
 * Git (GitHub/CLI/GitHooks/Workflows)
 * Git (GitHub/CLI/GitHooks/Workflows)
-* Docker & docker-compose
 * Agile, Kanban & Scrum
 * Agile, Kanban & Scrum
 * Project management tools (JIRA/Trello)
 * Project management tools (JIRA/Trello)
 * Building appliances/discreet servers (Raspberry Pi/Intel Atom SoC)
 * Building appliances/discreet servers (Raspberry Pi/Intel Atom SoC)
 * Analytics (Google/server stats/pingdom/custom built)
 * Analytics (Google/server stats/pingdom/custom built)
-* Basic server administration (nginx/Apache/caddy)
-* Versioning (npm/git tags/artifactory/docker images)
-* Linux/UNIX-like OSes (Debian/Arch/FreeBSD)
 * Bash (day to day environment & scripting)
 * Bash (day to day environment & scripting)
 * Python
 * Python
 
 

+ 891 - 0
package-lock.json

@@ -0,0 +1,891 @@
+{
+  "name": "CV",
+  "version": "0.0.1",
+  "lockfileVersion": 1,
+  "requires": true,
+  "dependencies": {
+    "abbrev": {
+      "version": "1.1.1",
+      "resolved": "https://registry.npmjs.org/abbrev/-/abbrev-1.1.1.tgz",
+      "integrity": "sha512-nne9/IiQ/hzIhY6pdDnbBtz7DjPTKrY00P/zvPSm5pOFkl6xuGrGnXn/VtTNNfNtAfZ9/1RtehkszU9qcTii0Q=="
+    },
+    "ajv": {
+      "version": "6.10.2",
+      "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.10.2.tgz",
+      "integrity": "sha512-TXtUUEYHuaTEbLZWIKUr5pmBuhDLy+8KYtPYdcV8qC+pOZL+NKqYwvWSRrVXHn+ZmRRAu8vJTAznH7Oag6RVRw==",
+      "optional": true,
+      "requires": {
+        "fast-deep-equal": "^2.0.1",
+        "fast-json-stable-stringify": "^2.0.0",
+        "json-schema-traverse": "^0.4.1",
+        "uri-js": "^4.2.2"
+      }
+    },
+    "asn1": {
+      "version": "0.2.4",
+      "resolved": "https://registry.npmjs.org/asn1/-/asn1-0.2.4.tgz",
+      "integrity": "sha512-jxwzQpLQjSmWXgwaCZE9Nz+glAG01yF1QnWgbhGwHI5A6FRIEY6IVqtHhIepHqI7/kyEyQEagBC5mBEFlIYvdg==",
+      "optional": true,
+      "requires": {
+        "safer-buffer": "~2.1.0"
+      }
+    },
+    "assert-plus": {
+      "version": "1.0.0",
+      "resolved": "https://registry.npmjs.org/assert-plus/-/assert-plus-1.0.0.tgz",
+      "integrity": "sha1-8S4PPF13sLHN2RRpQuTpbB5N1SU=",
+      "optional": true
+    },
+    "asynckit": {
+      "version": "0.4.0",
+      "resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz",
+      "integrity": "sha1-x57Zf380y48robyXkLzDZkdLS3k=",
+      "optional": true
+    },
+    "aws-sign2": {
+      "version": "0.7.0",
+      "resolved": "https://registry.npmjs.org/aws-sign2/-/aws-sign2-0.7.0.tgz",
+      "integrity": "sha1-tG6JCTSpWR8tL2+G1+ap8bP+dqg=",
+      "optional": true
+    },
+    "aws4": {
+      "version": "1.8.0",
+      "resolved": "https://registry.npmjs.org/aws4/-/aws4-1.8.0.tgz",
+      "integrity": "sha512-ReZxvNHIOv88FlT7rxcXIIC0fPt4KZqZbOlivyWtXLt8ESx84zd3kMC6iK5jVeS2qt+g7ftS7ye4fi06X5rtRQ==",
+      "optional": true
+    },
+    "bcrypt-pbkdf": {
+      "version": "1.0.2",
+      "resolved": "https://registry.npmjs.org/bcrypt-pbkdf/-/bcrypt-pbkdf-1.0.2.tgz",
+      "integrity": "sha1-pDAdOJtqQ/m2f/PKEaP2Y342Dp4=",
+      "optional": true,
+      "requires": {
+        "tweetnacl": "^0.14.3"
+      }
+    },
+    "buffer-from": {
+      "version": "1.1.1",
+      "resolved": "https://registry.npmjs.org/buffer-from/-/buffer-from-1.1.1.tgz",
+      "integrity": "sha512-MQcXEUbCKtEo7bhqEs6560Hyd4XaovZlO/k9V3hjVUF/zwW7KBVdSK4gIt/bzwS9MbR5qob+F5jusZsb0YQK2A==",
+      "optional": true
+    },
+    "camel-case": {
+      "version": "3.0.0",
+      "resolved": "https://registry.npmjs.org/camel-case/-/camel-case-3.0.0.tgz",
+      "integrity": "sha1-yjw2iKTpzzpM2nd9xNy8cTJJz3M=",
+      "requires": {
+        "no-case": "^2.2.0",
+        "upper-case": "^1.1.1"
+      }
+    },
+    "careless-fs": {
+      "version": "1.0.3",
+      "resolved": "https://registry.npmjs.org/careless-fs/-/careless-fs-1.0.3.tgz",
+      "integrity": "sha512-tMj0HKbhMkVZ+nedgUxTShb4AtFSxBX62PyEuTsVKCNbI0rCOSIwBnwgctRmZq2pM/WfScliCmESOW7qb+5KCA==",
+      "requires": {
+        "mkdirp": "^0.5.1"
+      }
+    },
+    "caseless": {
+      "version": "0.12.0",
+      "resolved": "https://registry.npmjs.org/caseless/-/caseless-0.12.0.tgz",
+      "integrity": "sha1-G2gcIf+EAzyCZUMJBolCDRhxUdw=",
+      "optional": true
+    },
+    "charset": {
+      "version": "1.0.1",
+      "resolved": "https://registry.npmjs.org/charset/-/charset-1.0.1.tgz",
+      "integrity": "sha512-6dVyOOYjpfFcL1Y4qChrAoQLRHvj2ziyhcm0QJlhOcAhykL/k1kTUPbeo+87MNRTRdk2OIIsIXbuF3x2wi5EXg=="
+    },
+    "clean-css": {
+      "version": "4.2.1",
+      "resolved": "https://registry.npmjs.org/clean-css/-/clean-css-4.2.1.tgz",
+      "integrity": "sha512-4ZxI6dy4lrY6FHzfiy1aEOXgu4LIsW2MhwG0VBKdcoGoH/XLFgaHSdLTGr4O8Be6A8r3MOphEiI8Gc1n0ecf3g==",
+      "requires": {
+        "source-map": "~0.6.0"
+      }
+    },
+    "combined-stream": {
+      "version": "1.0.8",
+      "resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz",
+      "integrity": "sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==",
+      "optional": true,
+      "requires": {
+        "delayed-stream": "~1.0.0"
+      }
+    },
+    "commander": {
+      "version": "2.20.3",
+      "resolved": "https://registry.npmjs.org/commander/-/commander-2.20.3.tgz",
+      "integrity": "sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ==",
+      "optional": true
+    },
+    "concat-stream": {
+      "version": "1.6.2",
+      "resolved": "https://registry.npmjs.org/concat-stream/-/concat-stream-1.6.2.tgz",
+      "integrity": "sha512-27HBghJxjiZtIk3Ycvn/4kbJk/1uZuJFfuPEns6LaEvpvG1f0hTea8lilrouyo9mVc2GWdcEZ8OLoGmSADlrCw==",
+      "optional": true,
+      "requires": {
+        "buffer-from": "^1.0.0",
+        "inherits": "^2.0.3",
+        "readable-stream": "^2.2.2",
+        "typedarray": "^0.0.6"
+      }
+    },
+    "core-util-is": {
+      "version": "1.0.2",
+      "resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.2.tgz",
+      "integrity": "sha1-tf1UIgqivFq1eqtxQMlAdUUDwac=",
+      "optional": true
+    },
+    "dashdash": {
+      "version": "1.14.1",
+      "resolved": "https://registry.npmjs.org/dashdash/-/dashdash-1.14.1.tgz",
+      "integrity": "sha1-hTz6D3y+L+1d4gMmuN1YEDX24vA=",
+      "optional": true,
+      "requires": {
+        "assert-plus": "^1.0.0"
+      }
+    },
+    "debug": {
+      "version": "2.6.9",
+      "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz",
+      "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==",
+      "optional": true,
+      "requires": {
+        "ms": "2.0.0"
+      }
+    },
+    "delayed-stream": {
+      "version": "1.0.0",
+      "resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz",
+      "integrity": "sha1-3zrhmayt+31ECqrgsp4icrJOxhk=",
+      "optional": true
+    },
+    "ecc-jsbn": {
+      "version": "0.1.2",
+      "resolved": "https://registry.npmjs.org/ecc-jsbn/-/ecc-jsbn-0.1.2.tgz",
+      "integrity": "sha1-OoOpBOVDUyh4dMVkt1SThoSamMk=",
+      "optional": true,
+      "requires": {
+        "jsbn": "~0.1.0",
+        "safer-buffer": "^2.1.0"
+      }
+    },
+    "ecstatic": {
+      "version": "4.1.2",
+      "resolved": "https://registry.npmjs.org/ecstatic/-/ecstatic-4.1.2.tgz",
+      "integrity": "sha512-lnrAOpU2f7Ra8dm1pW0D1ucyUxQIEk8RjFrvROg1YqCV0ueVu9hzgiSEbSyROqXDDiHREdqC4w3AwOTb23P4UQ==",
+      "requires": {
+        "charset": "^1.0.1",
+        "he": "^1.1.1",
+        "mime": "^2.4.1",
+        "minimist": "^1.1.0",
+        "on-finished": "^2.3.0",
+        "url-join": "^4.0.0"
+      },
+      "dependencies": {
+        "minimist": {
+          "version": "1.2.0",
+          "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.0.tgz",
+          "integrity": "sha1-o1AIsg9BOD7sH7kU9M1d95omQoQ="
+        }
+      }
+    },
+    "ee-first": {
+      "version": "1.1.1",
+      "resolved": "https://registry.npmjs.org/ee-first/-/ee-first-1.1.1.tgz",
+      "integrity": "sha1-WQxhFWsK4vTwJVcyoViyZrxWsh0="
+    },
+    "es6-promise": {
+      "version": "4.2.8",
+      "resolved": "https://registry.npmjs.org/es6-promise/-/es6-promise-4.2.8.tgz",
+      "integrity": "sha512-HJDGx5daxeIvxdBxvG2cb9g4tEvwIk3i8+nhX0yGrYmZUzbkdg8QbDevheDB8gd0//uPj4c1EQua8Q+MViT0/w==",
+      "optional": true
+    },
+    "extend": {
+      "version": "3.0.2",
+      "resolved": "https://registry.npmjs.org/extend/-/extend-3.0.2.tgz",
+      "integrity": "sha512-fjquC59cD7CyW6urNXK0FBufkZcoiGG80wTuPujX590cB5Ttln20E2UB4S/WARVqhXffZl2LNgS+gQdPIIim/g==",
+      "optional": true
+    },
+    "extract-zip": {
+      "version": "1.6.7",
+      "resolved": "https://registry.npmjs.org/extract-zip/-/extract-zip-1.6.7.tgz",
+      "integrity": "sha1-qEC0uK9kAyZMjbV/Txp0Mz74H+k=",
+      "optional": true,
+      "requires": {
+        "concat-stream": "1.6.2",
+        "debug": "2.6.9",
+        "mkdirp": "0.5.1",
+        "yauzl": "2.4.1"
+      }
+    },
+    "extsprintf": {
+      "version": "1.3.0",
+      "resolved": "https://registry.npmjs.org/extsprintf/-/extsprintf-1.3.0.tgz",
+      "integrity": "sha1-lpGEQOMEGnpBT4xS48V06zw+HgU=",
+      "optional": true
+    },
+    "fast-deep-equal": {
+      "version": "2.0.1",
+      "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-2.0.1.tgz",
+      "integrity": "sha1-ewUhjd+WZ79/Nwv3/bLLFf3Qqkk=",
+      "optional": true
+    },
+    "fast-json-stable-stringify": {
+      "version": "2.0.0",
+      "resolved": "https://registry.npmjs.org/fast-json-stable-stringify/-/fast-json-stable-stringify-2.0.0.tgz",
+      "integrity": "sha1-1RQsDK7msRifh9OnYREGT4bIu/I=",
+      "optional": true
+    },
+    "fd-slicer": {
+      "version": "1.0.1",
+      "resolved": "https://registry.npmjs.org/fd-slicer/-/fd-slicer-1.0.1.tgz",
+      "integrity": "sha1-i1vL2ewyfFBBv5qwI/1nUPEXfmU=",
+      "optional": true,
+      "requires": {
+        "pend": "~1.2.0"
+      }
+    },
+    "forever-agent": {
+      "version": "0.6.1",
+      "resolved": "https://registry.npmjs.org/forever-agent/-/forever-agent-0.6.1.tgz",
+      "integrity": "sha1-+8cfDEGt6zf5bFd60e1C2P2sypE=",
+      "optional": true
+    },
+    "form-data": {
+      "version": "2.3.3",
+      "resolved": "https://registry.npmjs.org/form-data/-/form-data-2.3.3.tgz",
+      "integrity": "sha512-1lLKB2Mu3aGP1Q/2eCOx0fNbRMe7XdwktwOruhfqqd0rIJWwN4Dh+E3hrPSlDCXnSR7UtZ1N38rVXm+6+MEhJQ==",
+      "optional": true,
+      "requires": {
+        "asynckit": "^0.4.0",
+        "combined-stream": "^1.0.6",
+        "mime-types": "^2.1.12"
+      }
+    },
+    "fs-extra": {
+      "version": "1.0.0",
+      "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-1.0.0.tgz",
+      "integrity": "sha1-zTzl9+fLYUWIP8rjGR6Yd/hYeVA=",
+      "optional": true,
+      "requires": {
+        "graceful-fs": "^4.1.2",
+        "jsonfile": "^2.1.0",
+        "klaw": "^1.0.0"
+      }
+    },
+    "getpass": {
+      "version": "0.1.7",
+      "resolved": "https://registry.npmjs.org/getpass/-/getpass-0.1.7.tgz",
+      "integrity": "sha1-Xv+OPmhNVprkyysSgmBOi6YhSfo=",
+      "optional": true,
+      "requires": {
+        "assert-plus": "^1.0.0"
+      }
+    },
+    "graceful-fs": {
+      "version": "4.2.3",
+      "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.3.tgz",
+      "integrity": "sha512-a30VEBm4PEdx1dRB7MFK7BejejvCvBronbLjht+sHuGYj8PHs7M/5Z+rt5lw551vZ7yfTCj4Vuyy3mSJytDWRQ==",
+      "optional": true
+    },
+    "grapefruit": {
+      "version": "github:leakypixel/grapefruit#85a03f60c8ad3eaee9cc09e530fce01c66ead7fc",
+      "from": "github:leakypixel/grapefruit"
+    },
+    "handlebars": {
+      "version": "4.5.1",
+      "resolved": "https://registry.npmjs.org/handlebars/-/handlebars-4.5.1.tgz",
+      "integrity": "sha512-C29UoFzHe9yM61lOsIlCE5/mQVGrnIOrOq7maQl76L7tYPCgC1og0Ajt6uWnX4ZTxBPnjw+CUvawphwCfJgUnA==",
+      "requires": {
+        "neo-async": "^2.6.0",
+        "optimist": "^0.6.1",
+        "source-map": "^0.6.1",
+        "uglify-js": "^3.1.4"
+      }
+    },
+    "har-schema": {
+      "version": "2.0.0",
+      "resolved": "https://registry.npmjs.org/har-schema/-/har-schema-2.0.0.tgz",
+      "integrity": "sha1-qUwiJOvKwEeCoNkDVSHyRzW37JI=",
+      "optional": true
+    },
+    "har-validator": {
+      "version": "5.1.3",
+      "resolved": "https://registry.npmjs.org/har-validator/-/har-validator-5.1.3.tgz",
+      "integrity": "sha512-sNvOCzEQNr/qrvJgc3UG/kD4QtlHycrzwS+6mfTrrSq97BvaYcPZZI1ZSqGSPR73Cxn4LKTD4PttRwfU7jWq5g==",
+      "optional": true,
+      "requires": {
+        "ajv": "^6.5.5",
+        "har-schema": "^2.0.0"
+      }
+    },
+    "hasha": {
+      "version": "2.2.0",
+      "resolved": "https://registry.npmjs.org/hasha/-/hasha-2.2.0.tgz",
+      "integrity": "sha1-eNfL/B5tZjA/55g3NlmEUXsvbuE=",
+      "optional": true,
+      "requires": {
+        "is-stream": "^1.0.1",
+        "pinkie-promise": "^2.0.0"
+      }
+    },
+    "he": {
+      "version": "1.2.0",
+      "resolved": "https://registry.npmjs.org/he/-/he-1.2.0.tgz",
+      "integrity": "sha512-F/1DnUGPopORZi0ni+CvrCgHQ5FyEAHRLSApuYWMmrbSwoN2Mn/7k+Gl38gJnR7yyDZk6WLXwiGod1JOWNDKGw=="
+    },
+    "html-minifier": {
+      "version": "3.5.21",
+      "resolved": "https://registry.npmjs.org/html-minifier/-/html-minifier-3.5.21.tgz",
+      "integrity": "sha512-LKUKwuJDhxNa3uf/LPR/KVjm/l3rBqtYeCOAekvG8F1vItxMUpueGd94i/asDDr8/1u7InxzFA5EeGjhhG5mMA==",
+      "requires": {
+        "camel-case": "3.0.x",
+        "clean-css": "4.2.x",
+        "commander": "2.17.x",
+        "he": "1.2.x",
+        "param-case": "2.1.x",
+        "relateurl": "0.2.x",
+        "uglify-js": "3.4.x"
+      },
+      "dependencies": {
+        "commander": {
+          "version": "2.17.1",
+          "resolved": "https://registry.npmjs.org/commander/-/commander-2.17.1.tgz",
+          "integrity": "sha512-wPMUt6FnH2yzG95SA6mzjQOEKUU3aLaDEmzs1ti+1E9h+CsrZghRlqEM/EJ4KscsQVG8uNN4uVreUeT8+drlgg=="
+        },
+        "uglify-js": {
+          "version": "3.4.10",
+          "resolved": "https://registry.npmjs.org/uglify-js/-/uglify-js-3.4.10.tgz",
+          "integrity": "sha512-Y2VsbPVs0FIshJztycsO2SfPk7/KAF/T72qzv9u5EpQ4kB2hQoHlhNQTsNyy6ul7lQtqJN/AoWeS23OzEiEFxw==",
+          "requires": {
+            "commander": "~2.19.0",
+            "source-map": "~0.6.1"
+          },
+          "dependencies": {
+            "commander": {
+              "version": "2.19.0",
+              "resolved": "https://registry.npmjs.org/commander/-/commander-2.19.0.tgz",
+              "integrity": "sha512-6tvAOO+D6OENvRAh524Dh9jcfKTYDQAqvqezbCW82xj5X0pSrcpxtvRKHLG0yBY6SD7PSDrJaj+0AiOcKVd1Xg=="
+            }
+          }
+        }
+      }
+    },
+    "html-pdf": {
+      "version": "2.2.0",
+      "resolved": "https://registry.npmjs.org/html-pdf/-/html-pdf-2.2.0.tgz",
+      "integrity": "sha1-S8+Rwky1YOR6o/rP0DPg4b8kG5E=",
+      "requires": {
+        "phantomjs-prebuilt": "^2.1.4"
+      }
+    },
+    "http-signature": {
+      "version": "1.2.0",
+      "resolved": "https://registry.npmjs.org/http-signature/-/http-signature-1.2.0.tgz",
+      "integrity": "sha1-muzZJRFHcvPZW2WmCruPfBj7rOE=",
+      "optional": true,
+      "requires": {
+        "assert-plus": "^1.0.0",
+        "jsprim": "^1.2.2",
+        "sshpk": "^1.7.0"
+      }
+    },
+    "inherits": {
+      "version": "2.0.4",
+      "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz",
+      "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==",
+      "optional": true
+    },
+    "is-stream": {
+      "version": "1.1.0",
+      "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-1.1.0.tgz",
+      "integrity": "sha1-EtSj3U5o4Lec6428hBc66A2RykQ=",
+      "optional": true
+    },
+    "is-typedarray": {
+      "version": "1.0.0",
+      "resolved": "https://registry.npmjs.org/is-typedarray/-/is-typedarray-1.0.0.tgz",
+      "integrity": "sha1-5HnICFjfDBsR3dppQPlgEfzaSpo=",
+      "optional": true
+    },
+    "isarray": {
+      "version": "1.0.0",
+      "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz",
+      "integrity": "sha1-u5NdSFgsuhaMBoNJV6VKPgcSTxE=",
+      "optional": true
+    },
+    "isexe": {
+      "version": "2.0.0",
+      "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz",
+      "integrity": "sha1-6PvzdNxVb/iUehDcsFctYz8s+hA=",
+      "optional": true
+    },
+    "isstream": {
+      "version": "0.1.2",
+      "resolved": "https://registry.npmjs.org/isstream/-/isstream-0.1.2.tgz",
+      "integrity": "sha1-R+Y/evVa+m+S4VAOaQ64uFKcCZo=",
+      "optional": true
+    },
+    "jsbn": {
+      "version": "0.1.1",
+      "resolved": "https://registry.npmjs.org/jsbn/-/jsbn-0.1.1.tgz",
+      "integrity": "sha1-peZUwuWi3rXyAdls77yoDA7y9RM=",
+      "optional": true
+    },
+    "json-schema": {
+      "version": "0.2.3",
+      "resolved": "https://registry.npmjs.org/json-schema/-/json-schema-0.2.3.tgz",
+      "integrity": "sha1-tIDIkuWaLwWVTOcnvT8qTogvnhM=",
+      "optional": true
+    },
+    "json-schema-traverse": {
+      "version": "0.4.1",
+      "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz",
+      "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==",
+      "optional": true
+    },
+    "json-stringify-safe": {
+      "version": "5.0.1",
+      "resolved": "https://registry.npmjs.org/json-stringify-safe/-/json-stringify-safe-5.0.1.tgz",
+      "integrity": "sha1-Epai1Y/UXxmg9s4B1lcB4sc1tus=",
+      "optional": true
+    },
+    "jsonfile": {
+      "version": "2.4.0",
+      "resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-2.4.0.tgz",
+      "integrity": "sha1-NzaitCi4e72gzIO1P6PWM6NcKug=",
+      "optional": true,
+      "requires": {
+        "graceful-fs": "^4.1.6"
+      }
+    },
+    "jsprim": {
+      "version": "1.4.1",
+      "resolved": "https://registry.npmjs.org/jsprim/-/jsprim-1.4.1.tgz",
+      "integrity": "sha1-MT5mvB5cwG5Di8G3SZwuXFastqI=",
+      "optional": true,
+      "requires": {
+        "assert-plus": "1.0.0",
+        "extsprintf": "1.3.0",
+        "json-schema": "0.2.3",
+        "verror": "1.10.0"
+      }
+    },
+    "kew": {
+      "version": "0.7.0",
+      "resolved": "https://registry.npmjs.org/kew/-/kew-0.7.0.tgz",
+      "integrity": "sha1-edk9LTM2PW/dKXCzNdkUGtWR15s=",
+      "optional": true
+    },
+    "klaw": {
+      "version": "1.3.1",
+      "resolved": "https://registry.npmjs.org/klaw/-/klaw-1.3.1.tgz",
+      "integrity": "sha1-QIhDO0azsbolnXh4XY6W9zugJDk=",
+      "optional": true,
+      "requires": {
+        "graceful-fs": "^4.1.9"
+      }
+    },
+    "lower-case": {
+      "version": "1.1.4",
+      "resolved": "https://registry.npmjs.org/lower-case/-/lower-case-1.1.4.tgz",
+      "integrity": "sha1-miyr0bno4K6ZOkv31YdcOcQujqw="
+    },
+    "markdown": {
+      "version": "0.5.0",
+      "resolved": "https://registry.npmjs.org/markdown/-/markdown-0.5.0.tgz",
+      "integrity": "sha1-KCBbVlqK51kt4gdGPWY33BgnIrI=",
+      "requires": {
+        "nopt": "~2.1.1"
+      }
+    },
+    "mime": {
+      "version": "2.4.4",
+      "resolved": "https://registry.npmjs.org/mime/-/mime-2.4.4.tgz",
+      "integrity": "sha512-LRxmNwziLPT828z+4YkNzloCFC2YM4wrB99k+AV5ZbEyfGNWfG8SO1FUXLmLDBSo89NrJZ4DIWeLjy1CHGhMGA=="
+    },
+    "mime-db": {
+      "version": "1.40.0",
+      "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.40.0.tgz",
+      "integrity": "sha512-jYdeOMPy9vnxEqFRRo6ZvTZ8d9oPb+k18PKoYNYUe2stVEBPPwsln/qWzdbmaIvnhZ9v2P+CuecK+fpUfsV2mA==",
+      "optional": true
+    },
+    "mime-types": {
+      "version": "2.1.24",
+      "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.24.tgz",
+      "integrity": "sha512-WaFHS3MCl5fapm3oLxU4eYDw77IQM2ACcxQ9RIxfaC3ooc6PFuBMGZZsYpvoXS5D5QTWPieo1jjLdAm3TBP3cQ==",
+      "optional": true,
+      "requires": {
+        "mime-db": "1.40.0"
+      }
+    },
+    "minimist": {
+      "version": "0.0.8",
+      "resolved": "https://registry.npmjs.org/minimist/-/minimist-0.0.8.tgz",
+      "integrity": "sha1-hX/Kv8M5fSYluCKCYuhqp6ARsF0="
+    },
+    "mkdirp": {
+      "version": "0.5.1",
+      "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.1.tgz",
+      "integrity": "sha1-MAV0OOrGz3+MR2fzhkjWaX11yQM=",
+      "requires": {
+        "minimist": "0.0.8"
+      }
+    },
+    "ms": {
+      "version": "2.0.0",
+      "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz",
+      "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=",
+      "optional": true
+    },
+    "neo-async": {
+      "version": "2.6.1",
+      "resolved": "https://registry.npmjs.org/neo-async/-/neo-async-2.6.1.tgz",
+      "integrity": "sha512-iyam8fBuCUpWeKPGpaNMetEocMt364qkCsfL9JuhjXX6dRnguRVOfk2GZaDpPjcOKiiXCPINZC1GczQ7iTq3Zw=="
+    },
+    "no-case": {
+      "version": "2.3.2",
+      "resolved": "https://registry.npmjs.org/no-case/-/no-case-2.3.2.tgz",
+      "integrity": "sha512-rmTZ9kz+f3rCvK2TD1Ue/oZlns7OGoIWP4fc3llxxRXlOkHKoWPPWJOfFYpITabSow43QJbRIoHQXtt10VldyQ==",
+      "requires": {
+        "lower-case": "^1.1.1"
+      }
+    },
+    "nopt": {
+      "version": "2.1.2",
+      "resolved": "https://registry.npmjs.org/nopt/-/nopt-2.1.2.tgz",
+      "integrity": "sha1-bMzZd7gBMqB3MdbozljCyDA8+a8=",
+      "requires": {
+        "abbrev": "1"
+      }
+    },
+    "oauth-sign": {
+      "version": "0.9.0",
+      "resolved": "https://registry.npmjs.org/oauth-sign/-/oauth-sign-0.9.0.tgz",
+      "integrity": "sha512-fexhUFFPTGV8ybAtSIGbV6gOkSv8UtRbDBnAyLQw4QPKkgNlsH2ByPGtMUqdWkos6YCRmAqViwgZrJc/mRDzZQ==",
+      "optional": true
+    },
+    "on-finished": {
+      "version": "2.3.0",
+      "resolved": "https://registry.npmjs.org/on-finished/-/on-finished-2.3.0.tgz",
+      "integrity": "sha1-IPEzZIGwg811M3mSoWlxqi2QaUc=",
+      "requires": {
+        "ee-first": "1.1.1"
+      }
+    },
+    "optimist": {
+      "version": "0.6.1",
+      "resolved": "https://registry.npmjs.org/optimist/-/optimist-0.6.1.tgz",
+      "integrity": "sha1-2j6nRob6IaGaERwybpDrFaAZZoY=",
+      "requires": {
+        "minimist": "~0.0.1",
+        "wordwrap": "~0.0.2"
+      }
+    },
+    "param-case": {
+      "version": "2.1.1",
+      "resolved": "https://registry.npmjs.org/param-case/-/param-case-2.1.1.tgz",
+      "integrity": "sha1-35T9jPZTHs915r75oIWPvHK+Ikc=",
+      "requires": {
+        "no-case": "^2.2.0"
+      }
+    },
+    "pend": {
+      "version": "1.2.0",
+      "resolved": "https://registry.npmjs.org/pend/-/pend-1.2.0.tgz",
+      "integrity": "sha1-elfrVQpng/kRUzH89GY9XI4AelA=",
+      "optional": true
+    },
+    "performance-now": {
+      "version": "2.1.0",
+      "resolved": "https://registry.npmjs.org/performance-now/-/performance-now-2.1.0.tgz",
+      "integrity": "sha1-Ywn04OX6kT7BxpMHrjZLSzd8nns=",
+      "optional": true
+    },
+    "phantomjs-prebuilt": {
+      "version": "2.1.16",
+      "resolved": "https://registry.npmjs.org/phantomjs-prebuilt/-/phantomjs-prebuilt-2.1.16.tgz",
+      "integrity": "sha1-79ISpKOWbTZHaE6ouniFSb4q7+8=",
+      "optional": true,
+      "requires": {
+        "es6-promise": "^4.0.3",
+        "extract-zip": "^1.6.5",
+        "fs-extra": "^1.0.0",
+        "hasha": "^2.2.0",
+        "kew": "^0.7.0",
+        "progress": "^1.1.8",
+        "request": "^2.81.0",
+        "request-progress": "^2.0.1",
+        "which": "^1.2.10"
+      }
+    },
+    "pinkie": {
+      "version": "2.0.4",
+      "resolved": "https://registry.npmjs.org/pinkie/-/pinkie-2.0.4.tgz",
+      "integrity": "sha1-clVrgM+g1IqXToDnckjoDtT3+HA=",
+      "optional": true
+    },
+    "pinkie-promise": {
+      "version": "2.0.1",
+      "resolved": "https://registry.npmjs.org/pinkie-promise/-/pinkie-promise-2.0.1.tgz",
+      "integrity": "sha1-ITXW36ejWMBprJsXh3YogihFD/o=",
+      "optional": true,
+      "requires": {
+        "pinkie": "^2.0.0"
+      }
+    },
+    "process-nextick-args": {
+      "version": "2.0.1",
+      "resolved": "https://registry.npmjs.org/process-nextick-args/-/process-nextick-args-2.0.1.tgz",
+      "integrity": "sha512-3ouUOpQhtgrbOa17J7+uxOTpITYWaGP7/AhoR3+A+/1e9skrzelGi/dXzEYyvbxubEF6Wn2ypscTKiKJFFn1ag==",
+      "optional": true
+    },
+    "progress": {
+      "version": "1.1.8",
+      "resolved": "https://registry.npmjs.org/progress/-/progress-1.1.8.tgz",
+      "integrity": "sha1-4mDHj2Fhzdmw5WzD4Khd4Xx6V74=",
+      "optional": true
+    },
+    "psl": {
+      "version": "1.4.0",
+      "resolved": "https://registry.npmjs.org/psl/-/psl-1.4.0.tgz",
+      "integrity": "sha512-HZzqCGPecFLyoRj5HLfuDSKYTJkAfB5thKBIkRHtGjWwY7p1dAyveIbXIq4tO0KYfDF2tHqPUgY9SDnGm00uFw==",
+      "optional": true
+    },
+    "punycode": {
+      "version": "2.1.1",
+      "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.1.1.tgz",
+      "integrity": "sha512-XRsRjdf+j5ml+y/6GKHPZbrF/8p2Yga0JPtdqTIY2Xe5ohJPD9saDJJLPvp9+NSBprVvevdXZybnj2cv8OEd0A==",
+      "optional": true
+    },
+    "qs": {
+      "version": "6.5.2",
+      "resolved": "https://registry.npmjs.org/qs/-/qs-6.5.2.tgz",
+      "integrity": "sha512-N5ZAX4/LxJmF+7wN74pUD6qAh9/wnvdQcjq9TZjevvXzSUo7bfmw91saqMjzGS2xq91/odN2dW/WOl7qQHNDGA==",
+      "optional": true
+    },
+    "readable-stream": {
+      "version": "2.3.6",
+      "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.6.tgz",
+      "integrity": "sha512-tQtKA9WIAhBF3+VLAseyMqZeBjW0AHJoxOtYqSUZNJxauErmLbVm2FW1y+J/YA9dUrAC39ITejlZWhVIwawkKw==",
+      "optional": true,
+      "requires": {
+        "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"
+      }
+    },
+    "relateurl": {
+      "version": "0.2.7",
+      "resolved": "https://registry.npmjs.org/relateurl/-/relateurl-0.2.7.tgz",
+      "integrity": "sha1-VNvzd+UUQKypCkzSdGANP/LYiKk="
+    },
+    "request": {
+      "version": "2.88.0",
+      "resolved": "https://registry.npmjs.org/request/-/request-2.88.0.tgz",
+      "integrity": "sha512-NAqBSrijGLZdM0WZNsInLJpkJokL72XYjUpnB0iwsRgxh7dB6COrHnTBNwN0E+lHDAJzu7kLAkDeY08z2/A0hg==",
+      "optional": true,
+      "requires": {
+        "aws-sign2": "~0.7.0",
+        "aws4": "^1.8.0",
+        "caseless": "~0.12.0",
+        "combined-stream": "~1.0.6",
+        "extend": "~3.0.2",
+        "forever-agent": "~0.6.1",
+        "form-data": "~2.3.2",
+        "har-validator": "~5.1.0",
+        "http-signature": "~1.2.0",
+        "is-typedarray": "~1.0.0",
+        "isstream": "~0.1.2",
+        "json-stringify-safe": "~5.0.1",
+        "mime-types": "~2.1.19",
+        "oauth-sign": "~0.9.0",
+        "performance-now": "^2.1.0",
+        "qs": "~6.5.2",
+        "safe-buffer": "^5.1.2",
+        "tough-cookie": "~2.4.3",
+        "tunnel-agent": "^0.6.0",
+        "uuid": "^3.3.2"
+      }
+    },
+    "request-progress": {
+      "version": "2.0.1",
+      "resolved": "https://registry.npmjs.org/request-progress/-/request-progress-2.0.1.tgz",
+      "integrity": "sha1-XTa7V5YcZzqlt4jbyBQf3yO0Tgg=",
+      "optional": true,
+      "requires": {
+        "throttleit": "^1.0.0"
+      }
+    },
+    "safe-buffer": {
+      "version": "5.1.2",
+      "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz",
+      "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==",
+      "optional": true
+    },
+    "safer-buffer": {
+      "version": "2.1.2",
+      "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz",
+      "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==",
+      "optional": true
+    },
+    "source-map": {
+      "version": "0.6.1",
+      "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz",
+      "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g=="
+    },
+    "sshpk": {
+      "version": "1.16.1",
+      "resolved": "https://registry.npmjs.org/sshpk/-/sshpk-1.16.1.tgz",
+      "integrity": "sha512-HXXqVUq7+pcKeLqqZj6mHFUMvXtOJt1uoUx09pFW6011inTMxqI8BA8PM95myrIyyKwdnzjdFjLiE6KBPVtJIg==",
+      "optional": true,
+      "requires": {
+        "asn1": "~0.2.3",
+        "assert-plus": "^1.0.0",
+        "bcrypt-pbkdf": "^1.0.0",
+        "dashdash": "^1.12.0",
+        "ecc-jsbn": "~0.1.1",
+        "getpass": "^0.1.1",
+        "jsbn": "~0.1.0",
+        "safer-buffer": "^2.0.2",
+        "tweetnacl": "~0.14.0"
+      }
+    },
+    "string_decoder": {
+      "version": "1.1.1",
+      "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz",
+      "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==",
+      "optional": true,
+      "requires": {
+        "safe-buffer": "~5.1.0"
+      }
+    },
+    "throttleit": {
+      "version": "1.0.0",
+      "resolved": "https://registry.npmjs.org/throttleit/-/throttleit-1.0.0.tgz",
+      "integrity": "sha1-nnhYNtr0Z0MUWlmEtiaNgoUorGw=",
+      "optional": true
+    },
+    "tough-cookie": {
+      "version": "2.4.3",
+      "resolved": "https://registry.npmjs.org/tough-cookie/-/tough-cookie-2.4.3.tgz",
+      "integrity": "sha512-Q5srk/4vDM54WJsJio3XNn6K2sCG+CQ8G5Wz6bZhRZoAe/+TxjWB/GlFAnYEbkYVlON9FMk/fE3h2RLpPXo4lQ==",
+      "optional": true,
+      "requires": {
+        "psl": "^1.1.24",
+        "punycode": "^1.4.1"
+      },
+      "dependencies": {
+        "punycode": {
+          "version": "1.4.1",
+          "resolved": "https://registry.npmjs.org/punycode/-/punycode-1.4.1.tgz",
+          "integrity": "sha1-wNWmOycYgArY4esPpSachN1BhF4=",
+          "optional": true
+        }
+      }
+    },
+    "tunnel-agent": {
+      "version": "0.6.0",
+      "resolved": "https://registry.npmjs.org/tunnel-agent/-/tunnel-agent-0.6.0.tgz",
+      "integrity": "sha1-J6XeoGs2sEoKmWZ3SykIaPD8QP0=",
+      "optional": true,
+      "requires": {
+        "safe-buffer": "^5.0.1"
+      }
+    },
+    "tweetnacl": {
+      "version": "0.14.5",
+      "resolved": "https://registry.npmjs.org/tweetnacl/-/tweetnacl-0.14.5.tgz",
+      "integrity": "sha1-WuaBd/GS1EViadEIr6k/+HQ/T2Q=",
+      "optional": true
+    },
+    "typedarray": {
+      "version": "0.0.6",
+      "resolved": "https://registry.npmjs.org/typedarray/-/typedarray-0.0.6.tgz",
+      "integrity": "sha1-hnrHTjhkGHsdPUfZlqeOxciDB3c=",
+      "optional": true
+    },
+    "uglify-js": {
+      "version": "3.6.8",
+      "resolved": "https://registry.npmjs.org/uglify-js/-/uglify-js-3.6.8.tgz",
+      "integrity": "sha512-XhHJ3S3ZyMwP8kY1Gkugqx3CJh2C3O0y8NPiSxtm1tyD/pktLAkFZsFGpuNfTZddKDQ/bbDBLAd2YyA1pbi8HQ==",
+      "optional": true,
+      "requires": {
+        "commander": "~2.20.3",
+        "source-map": "~0.6.1"
+      }
+    },
+    "upper-case": {
+      "version": "1.1.3",
+      "resolved": "https://registry.npmjs.org/upper-case/-/upper-case-1.1.3.tgz",
+      "integrity": "sha1-9rRQHC7EzdJrp4vnIilh3ndiFZg="
+    },
+    "uri-js": {
+      "version": "4.2.2",
+      "resolved": "https://registry.npmjs.org/uri-js/-/uri-js-4.2.2.tgz",
+      "integrity": "sha512-KY9Frmirql91X2Qgjry0Wd4Y+YTdrdZheS8TFwvkbLWf/G5KNJDCh6pKL5OZctEW4+0Baa5idK2ZQuELRwPznQ==",
+      "optional": true,
+      "requires": {
+        "punycode": "^2.1.0"
+      }
+    },
+    "url-join": {
+      "version": "4.0.1",
+      "resolved": "https://registry.npmjs.org/url-join/-/url-join-4.0.1.tgz",
+      "integrity": "sha512-jk1+QP6ZJqyOiuEI9AEWQfju/nB2Pw466kbA0LEZljHwKeMgd9WrAEgEGxjPDD2+TNbbb37rTyhEfrCXfuKXnA=="
+    },
+    "util-deprecate": {
+      "version": "1.0.2",
+      "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz",
+      "integrity": "sha1-RQ1Nyfpw3nMnYvvS1KKJgUGaDM8=",
+      "optional": true
+    },
+    "uuid": {
+      "version": "3.3.3",
+      "resolved": "https://registry.npmjs.org/uuid/-/uuid-3.3.3.tgz",
+      "integrity": "sha512-pW0No1RGHgzlpHJO1nsVrHKpOEIxkGg1xB+v0ZmdNH5OAeAwzAVrCnI2/6Mtx+Uys6iaylxa+D3g4j63IKKjSQ==",
+      "optional": true
+    },
+    "verror": {
+      "version": "1.10.0",
+      "resolved": "https://registry.npmjs.org/verror/-/verror-1.10.0.tgz",
+      "integrity": "sha1-OhBcoXBTr1XW4nDB+CiGguGNpAA=",
+      "optional": true,
+      "requires": {
+        "assert-plus": "^1.0.0",
+        "core-util-is": "1.0.2",
+        "extsprintf": "^1.2.0"
+      }
+    },
+    "which": {
+      "version": "1.3.1",
+      "resolved": "https://registry.npmjs.org/which/-/which-1.3.1.tgz",
+      "integrity": "sha512-HxJdYWq1MTIQbJ3nw0cqssHoTNU267KlrDuGZ1WYlxDStUtKUhOaJmh112/TZmHxxUfuJqPXSOm7tDyas0OSIQ==",
+      "optional": true,
+      "requires": {
+        "isexe": "^2.0.0"
+      }
+    },
+    "wordwrap": {
+      "version": "0.0.3",
+      "resolved": "https://registry.npmjs.org/wordwrap/-/wordwrap-0.0.3.tgz",
+      "integrity": "sha1-o9XabNXAvAAI03I0u68b7WMFkQc="
+    },
+    "yauzl": {
+      "version": "2.4.1",
+      "resolved": "https://registry.npmjs.org/yauzl/-/yauzl-2.4.1.tgz",
+      "integrity": "sha1-lSj0QtqxsihOWLQ3m7GU4i4MQAU=",
+      "optional": true,
+      "requires": {
+        "fd-slicer": "~1.0.1"
+      }
+    }
+  }
+}

+ 20 - 0
package.json

@@ -0,0 +1,20 @@
+{
+  "name": "CV",
+  "version": "0.0.1",
+  "description": "Craig Fletcher's CV",
+  "scripts": {
+    "build": "node .npm-scripts/build.js",
+    "serve": "ecstatic output/"
+  },
+  "author": "Craig Fletcher <hi@leakypixel.net>",
+  "dependencies": {
+    "careless-fs": "^1.0.0",
+    "ecstatic": "^4.1.2",
+    "grapefruit": "github:leakypixel/grapefruit",
+    "handlebars": "^4.0.12",
+    "html-minifier": "^3.5.21",
+    "html-pdf": "^2.2.0",
+    "markdown": "^0.5.0"
+  },
+  "homepage": "https://leakypixel.net/"
+}

+ 8 - 2
styles.css

@@ -36,7 +36,7 @@ section img {
 
 
 h1 {
 h1 {
   text-transform: uppercase;
   text-transform: uppercase;
-  display: inline;
+  display: inline-block;
   font-family: "Gidole", monospace;
   font-family: "Gidole", monospace;
   font-weight: bold;
   font-weight: bold;
   font-weight: 600;
   font-weight: 600;
@@ -45,7 +45,7 @@ h1 {
   line-height: 1.6em;
   line-height: 1.6em;
   border-bottom: 0.4em solid;
   border-bottom: 0.4em solid;
   border-color: var(--color-accent-2);
   border-color: var(--color-accent-2);
-  margin: 0 0 1em 0;
+  margin: 1em 0 2em 0;
 }
 }
 
 
 h2 {
 h2 {
@@ -136,6 +136,12 @@ header {
     width: 100%;
     width: 100%;
     margin: 0;
     margin: 0;
   }
   }
+  body {
+    max-width: 95%;
+    width: 95%;
+    margin: 1em;
+    font-size: 14px;
+  }
 }
 }
 /* Fonts */
 /* Fonts */
 @import url("https://fonts.googleapis.com/css?family=Noto+Sans");
 @import url("https://fonts.googleapis.com/css?family=Noto+Sans");

+ 15 - 0
template.handlebars

@@ -0,0 +1,15 @@
+<!DOCTYPE html>
+<html>
+<head>
+<title>Craig Fletcher's CV</title>
+{{#if meta.styles}}
+<style type="text/css">
+{{{ meta.styles.content }}}
+</style> {{/if}}
+</head>
+<body>
+  <section>
+  {{{ item.content }}}
+  </section>
+</body>
+</html>