[
  {
    "path": ".eslintrc.js",
    "content": "/* eslint-env node */\n\nmodule.exports = {\n  plugins: [ 'metafizzy' ],\n  extends: 'plugin:metafizzy/browser',\n  env: {\n    browser: true,\n    commonjs: true,\n  },\n  parserOptions: {\n    ecmaVersion: 5,\n  },\n  rules: {\n    'no-var': 'off',\n    'max-params': [ 'error', {\n      max: 5,\n    } ],\n  },\n};\n"
  },
  {
    "path": ".github/contributing.md",
    "content": "## Feature requests\n\n**Add 👍 reaction** to issues for features you would like to see added to Zdog. Do not add +1 comments — [they will be deleted](https://metafizzy.co/blog/use-github-reactions-delete-plus-1-comments/).\n\n## Reduced test cases required\n\nAll bug reports and problem issues require a [**reduced test case**](https://css-tricks.com/reduced-test-cases/). Create one by forking this any one of the [Zdog demos on CodePen](https://codepen.io/desandro/pens/tags/?grid_type=list&selected_tag=zdog-v1-docs&sort_col=created_at&sort_order=asc).\n\n**CodePen**\n\n+ [Hello world canvas](https://codepen.io/desandro/pen/YbrLaO)\n+ [Hello world SVG](https://codepen.io/desandro/pen/Bewxme)\n+ [resize fullscreen](https://codepen.io/desandro/pen/dEJxaV)\n+ [Strutter](https://codepen.io/desandro/pen/xNPaoP)\n\n**Test cases**\n\n+ A reduced test case clearly demonstrates the bug or issue.\n+ It contains the bare minimum HTML, CSS, and JavaScript required to demonstrate the bug.\n+ A link to your production site is **not** a reduced test case.\n\nProviding a reduced test case is the best way to get your issue addressed. They help you point out the problem. They help me verify and debug the problem. They help others understand the problem. Without a reduced test case, your issue may be closed.\n\n## Pull requests\n\nContributions are welcome! \n\n+ **For typos and one-line fixes,** send those right in.\n+ **For larger features,** open an issue before starting any significant work. Let's discuss to see how your feature fits within Zdog's vision.\n+ **Follow the code style.** Spaces in brackets, semicolons, trailing commas.\n+ **Do not edit `dist/` files.** Make your edits to source files in `js/` and `css/`.\n+ **Do not run `make` to update `dist/` files.** I'll take care of this when I create a new release.\n"
  },
  {
    "path": ".github/issue_template.md",
    "content": "<!-- Thanks for submitting an issue! If you have a bug or problem issue, please include a **reduced test case**. Create one in CodePen or other demo site. See contributing guidelines. -->\n\n**Test case:** https://codepen.io/desandro/pen/YbrLaO\n"
  },
  {
    "path": ".gitignore",
    "content": ".DS_Store\nnode_modules/\nbower_components/\n"
  },
  {
    "path": "README.md",
    "content": "# Zdog\n\n_Round, flat, designer-friendly pseudo-3D engine_\n\nView complete documentation and live demos at [zzz.dog](https://zzz.dog).\n\n## Install\n\n### Download\n\n+ [zdog.dist.min.js](https://unpkg.com/zdog@1/dist/zdog.dist.min.js) minified, or\n+ [zdog.dist.js](https://unpkg.com/zdog@1/dist/zdog.dist.js) un-minified\n\n### CDN\n\nLink directly to Zdog JS on [unpkg](https://unpkg.com).\n\n``` html\n<script src=\"https://unpkg.com/zdog@1/dist/zdog.dist.min.js\"></script>\n```\n\n### Package managers\n\nnpm: `npm install zdog`\n\nBower: `bower install zdog`\n\n## Hello world demo\n\nCreate 3D models with Zdog by adding shapes. See [Getting started](https://zzz.dog/getting-started) for a walk-through of this demo.\n\n``` js\nlet isSpinning = true;\n\nlet illo = new Zdog.Illustration({\n  element: '.zdog-canvas',\n  zoom: 4,\n  dragRotate: true,\n  // stop spinning when drag starts\n  onDragStart: function() {\n    isSpinning = false;\n  },\n});\n\n// circle\nnew Zdog.Ellipse({\n  addTo: illo,\n  diameter: 20,\n  translate: { z: 10 },\n  stroke: 5,\n  color: '#636',\n});\n\n// square\nnew Zdog.Rect({\n  addTo: illo,\n  width: 20,\n  height: 20,\n  translate: { z: -10 },\n  stroke: 3,\n  color: '#E62',\n  fill: true,\n});\n\nfunction animate() {\n  illo.rotate.y += isSpinning ? 0.03 : 0;\n  illo.updateRenderGraph();\n  requestAnimationFrame( animate );\n}\nanimate();\n```\n\n## About Zdog\n\nHi, [Dave here](https://desandro.com). I wanted to make a video game. I needed a 3D engine, but most engines were too powerful and complex for me. I made Zdog so I could design and display simple 3D models without a lot of overhead.\n\nZdog is directly inspired by [Dogz](https://en.wikipedia.org/wiki/Petz), a virtual pet game by P.F. Magic released in 1995. It used flat 2D circle sprites to render the Dogz’ models, but in a 3D scene. [See Dogz playthrough video here.](https://www.youtube.com/watch?v=6lKSn_cHw5k) Dogz were fully animated in real time, running, flopping, scratching (on Windows 3.1!). It was remarkable.\n\nZdog uses the same principle. It renders all shapes using the 2D drawing APIs in either `<canvas>` or `<svg>`. Spheres are actually dots. Toruses are actually circles. Capsules are actually thick lines. It’s a simple, but effective trick. The underlying 3D math comes from [Rotating 3D Shapes](https://www.khanacademy.org/computing/computer-programming/programming-games-visualizations/programming-3d-shapes/a/rotating-3d-shapes) by [Peter Collingridge](https://petercollingridge.appspot.com/3D-tutorial/rotating-objects).\n\nZdog is pronounced \"Zee-dog\" in American parlance or \"Zed-dog\" in British.\n\n### Beta!\n\nZdog v1 is a beta-release, of sorts. This is my first time creating a 3D engine, so I probably got some stuff wrong. Expect lots of changes for v2. Provide input and select new features on the [Zdog issue tracker on GitHub](https://github.com/metafizzy/zdog/issues).\n\n### More Zdog resources\n\nOther people's stuff:\n\n+ [Zfont](https://jaames.github.io/zfont/) - Text plugin for Zdog\n+ [vue-zdog](https://github.com/AlexandreBonaventure/vue-zdog) - Vue wrapper for Zdog\n+ [zDogPy](https://github.com/gferreira/zdogpy) - Python port of Zdog for DrawBot\n+ [Made with Zdog CodePen Collection](https://codepen.io/collection/DzdGMe/)\n+ [Made with Zdog on Twitter](https://twitter.com/i/moments/1135000612356206592)\n\nMy stuff:\n\n+ [Zdog demos on CodePen](https://github.com/metafizzy/zdog-demos), source code at [zdog-demos](https://github.com/metafizzy/zdog-demos)\n+ [zdog-docs](https://github.com/metafizzy/zdog-docs) - Docs site source code\n\n---\n\nLicensed MIT. Made by Metafizzy 🌈🐻\n"
  },
  {
    "path": "bower.json",
    "content": "{\n  \"name\": \"zdog\",\n  \"description\": \"Round, flat, designer-friendly pseudo-3D engine\",\n  \"main\": \"js/index.js\",\n  \"authors\": [\n    \"David DeSandro\"\n  ],\n  \"license\": \"MIT\",\n  \"keywords\": [\n    \"3D\",\n    \"canvas\",\n    \"svg\"\n  ],\n  \"homepage\": \"https://zzz.dog\",\n  \"ignore\": [\n    \"**/.*\",\n    \"node_modules\",\n    \"bower_components\",\n    \"test\",\n    \"tests\",\n    \"package.json\",\n    \"demo\",\n    \"demos\"\n  ]\n}\n"
  },
  {
    "path": "demos/.eslintrc.js",
    "content": "/* eslint-env node */\n\nmodule.exports = {\n  extends: '../.eslintrc.js',\n  globals: {\n    Zdog: 'readonly',\n  },\n  rules: {\n    'key-spacing': 'off',\n    'max-lines': 'off',\n  },\n};\n"
  },
  {
    "path": "demos/box-cross/box-cross.js",
    "content": "// ----- setup ----- //\n\nvar sceneSize = 9;\nvar isSpinning = true;\nvar TAU = Zdog.TAU;\n// colors\nvar yellow = '#ED0';\nvar gold = '#EA0';\nvar orange = '#E62';\nvar garnet = '#C25';\nvar eggplant = '#636';\n\nvar initRotate = { x: ( 35/360 ) * TAU, y: TAU/8 };\n\nvar illo = new Zdog.Illustration({\n  element: '.illo',\n  rotate: initRotate,\n  resize: 'fullscreen',\n  dragRotate: true,\n  onDragStart: function() {\n    isSpinning = false;\n  },\n  onResize: function( width, height ) {\n    this.zoom = Math.floor( Math.min( width, height ) / sceneSize );\n  },\n});\n\n// ----- model ----- //\n\nvar model = new Zdog.Anchor({\n  addTo: illo,\n});\n\nfunction addBox( options ) {\n  var boxOptions = {\n    addTo: model,\n    stroke: false,\n    topFace: yellow,\n    rearFace: gold,\n    leftFace: orange,\n    rightFace: orange,\n    frontFace: garnet,\n    bottomFace: eggplant,\n  };\n  Zdog.extend( boxOptions, options );\n\n  new Zdog.Box( boxOptions );\n}\n\n// top\naddBox({\n  bottomFace: false,\n  translate: { y: -1 },\n});\n// bottom\naddBox({\n  topFace: false,\n  translate: { y: 1 },\n});\n// front\naddBox({\n  rearFace: false,\n  translate: { z: 1 },\n});\n// back\naddBox({\n  frontFace: false,\n  translate: { z: -1 },\n});\n// left\naddBox({\n  rightFace: false,\n  translate: { x: -1 },\n});\n// right\naddBox({\n  leftFace: false,\n  translate: { x: 1 },\n});\n\nvar dot = new Zdog.Shape({\n  addTo: model,\n  translate: { y: -2 },\n  stroke: 1,\n  color: gold,\n});\ndot.copy({\n  translate: { y: 2 },\n  color: gold,\n});\ndot.copy({\n  translate: { x: -2 },\n  color: yellow,\n});\ndot.copy({\n  translate: { x: 2 },\n  color: garnet,\n});\ndot.copy({\n  translate: { z: -2 },\n  color: orange,\n});\ndot.copy({\n  translate: { z: 2 },\n  color: eggplant,\n});\n\n// ----- animate ----- //\n\nvar ticker = 0;\nvar cycleCount = 150;\n\nfunction animate() {\n  spin();\n  illo.updateRenderGraph();\n  requestAnimationFrame( animate );\n}\n\nfunction spin() {\n  if ( !isSpinning ) {\n    return;\n  }\n  var progress = ticker/cycleCount;\n  var turn = Math.floor( progress % 4 );\n  var theta = Zdog.easeInOut( progress % 1, 3 ) * TAU;\n  if ( turn == 0 || turn == 2 ) {\n    model.rotate.y = theta;\n  } else if ( turn == 1 ) {\n    model.rotate.x = theta;\n  } else if ( turn == 3 ) {\n    model.rotate.z = theta;\n  }\n  ticker++;\n}\n\nanimate();\n\n"
  },
  {
    "path": "demos/box-cross/index.html",
    "content": "<!doctype html>\n<html lang=\"en\">\n<head>\n  <meta charset=\"utf-8\" />\n  <meta name=\"viewport\" content=\"width=device-width\" />\n\n  <title>box cross</title>\n\n  <style>\n    html { height: 100%; }\n\n    body {\n      min-height: 100%;\n      margin: 0;\n    }\n\n    .illo {\n      display: block;\n      width: 100%;\n      height: 100%;\n      background: #FDB;\n      cursor: move;\n    }\n  </style>\n\n</head>\n<body>\n\n\n<canvas class=\"illo\"></canvas>\n\n<script src=\"../../js/boilerplate.js\"></script>\n<script src=\"../../js/canvas-renderer.js\"></script>\n<script src=\"../../js/vector.js\"></script>\n<script src=\"../../js/anchor.js\"></script>\n<script src=\"../../js/path-command.js\"></script>\n<script src=\"../../js/shape.js\"></script>\n<script src=\"../../js/rect.js\"></script>\n<script src=\"../../js/box.js\"></script>\n<script src=\"../../js/dragger.js\"></script>\n<script src=\"../../js/illustration.js\"></script>\n<script src=\"box-cross.js\"></script>\n\n</body>\n</html>\n"
  },
  {
    "path": "demos/dot-cube/dot-cube.js",
    "content": "// ----- setup ----- //\n\nvar sceneSize = 24;\nvar TAU = Zdog.TAU;\n\nvar illo = new Zdog.Illustration({\n  element: '.illo',\n  rotate: { x: TAU * -35/360, y: TAU * 1/8 },\n  dragRotate: true,\n  resize: 'fullscreen',\n  onResize: function( width, height ) {\n    this.zoom = Math.floor( Math.min( width, height ) / sceneSize );\n  },\n});\n\n// ----- model ----- //\n\nvar cube = new Zdog.Anchor({\n  addTo: illo,\n  scale: 4,\n});\n\nvar oneUnit = new Zdog.Vector({ x: 1, y: 1 });\n\nvar side = new Zdog.Anchor({\n  addTo: cube,\n  translate: { z: 1 },\n});\n\nvar dot = new Zdog.Shape({\n  addTo: side,\n  translate: oneUnit.copy(),\n  stroke: 1,\n  color: 'white',\n});\n\ndot.copy({ translate: { x: -1, y:  1 } });\ndot.copy({ translate: { x:  1, y: -1 } });\ndot.copy({ translate: { x: -1, y: -1 } });\n\n// more dots\ndot.copy({ translate: { x:  1 } });\ndot.copy({ translate: { x: -1 } });\ndot.copy({ translate: { y: -1 } });\ndot.copy({ translate: { y:  1 } });\n\nside.copyGraph({\n  translate: { z: -1 },\n});\n\nvar midDot = dot.copy({\n  addTo: cube,\n});\n\nmidDot.copy({ translate: { x: -1, y:  1 } });\nmidDot.copy({ translate: { x:  1, y: -1 } });\nmidDot.copy({ translate: { x: -1, y: -1 } });\n\n// ----- animate ----- //\n\nvar keyframes = [\n  { x: 0, y: 0, z: 0 },\n  { x: 0, y: 0, z: TAU/4 },\n  { x: -TAU/4, y: 0, z: TAU/4 },\n  { x: -TAU/4, y: 0, z: TAU/2 },\n];\n\nvar ticker = 0;\nvar cycleCount = 75;\nvar turnLimit = keyframes.length - 1;\n\nfunction animate() {\n  var progress = ticker/cycleCount;\n  var tween = Zdog.easeInOut( progress % 1, 4 );\n  var turn = Math.floor( progress % turnLimit );\n  var keyA = keyframes[ turn ];\n  var keyB = keyframes[ turn + 1 ];\n  cube.rotate.x = Zdog.lerp( keyA.x, keyB.x, tween );\n  cube.rotate.y = Zdog.lerp( keyA.y, keyB.y, tween );\n  cube.rotate.z = Zdog.lerp( keyA.z, keyB.z, tween );\n  ticker++;\n\n  illo.updateRenderGraph();\n  requestAnimationFrame( animate );\n}\n\nanimate();\n\n"
  },
  {
    "path": "demos/dot-cube/index.html",
    "content": "<!doctype html>\n<html lang=\"en\">\n<head>\n  <meta charset=\"utf-8\" />\n  <meta name=\"viewport\" content=\"width=device-width\" />\n\n  <title>dot cube</title>\n\n  <style>\n    html { height: 100%; }\n\n    body {\n      min-height: 100%;\n      margin: 0;\n      background: black;\n      color: white;\n    }\n\n    .illo {\n      display: block;\n      width: 100%;\n      height: 100%;\n      cursor: move;\n    }\n  </style>\n\n</head>\n<body>\n\n<canvas class=\"illo\"></canvas>\n\n<script src=\"../../js/boilerplate.js\"></script>\n<script src=\"../../js/canvas-renderer.js\"></script>\n<script src=\"../../js/vector.js\"></script>\n<script src=\"../../js/anchor.js\"></script>\n<script src=\"../../js/path-command.js\"></script>\n<script src=\"../../js/shape.js\"></script>\n<script src=\"../../js/group.js\"></script>\n<script src=\"../../js/dragger.js\"></script>\n<script src=\"../../js/illustration.js\"></script>\n<script src=\"dot-cube.js\"></script>\n\n</body>\n</html>\n"
  },
  {
    "path": "demos/fullscreen/fullscreen.js",
    "content": "// ----- setup ----- //\n\nvar isSpinning = true;\nvar gold = '#EA0';\nvar orange = '#E62';\nvar garnet = '#C25';\nvar eggplant = '#636';\n\nvar illo = new Zdog.Illustration({\n  element: '.illo',\n  zoom: 4,\n  resize: 'fullscreen',\n  dragRotate: true,\n  onDragStart: function() {\n    isSpinning = false;\n  },\n  onResize: function( width, height ) {\n    this.zoom = Math.min( width, height ) / 50;\n  },\n});\n\n// ----- model ----- //\n\nnew Zdog.Rect({\n  width: 20,\n  height: 20,\n  addTo: illo,\n  translate: { z: -10 },\n  stroke: 2,\n  color: garnet,\n});\n\nnew Zdog.Ellipse({\n  diameter: 16,\n  addTo: illo,\n  translate: { z: 10 },\n  stroke: 4,\n  color: eggplant,\n});\n\nnew Zdog.Shape({\n  path: [\n    { x:  0, z:  1 },\n    { x: -1, z: -1 },\n    { x:  1, z: -1 },\n  ],\n  scale: { x: 5, z: 5 },\n  addTo: illo,\n  stroke: 2,\n  fill: true,\n  color: gold,\n});\n\nnew Zdog.Shape({\n  translate: { x: 10, y: -5 },\n  addTo: illo,\n  stroke: 7,\n  color: orange,\n});\n\n// ----- animate ----- //\n\nfunction animate() {\n  illo.rotate.y += isSpinning ? 0.03 : 0;\n  illo.updateRenderGraph();\n  requestAnimationFrame( animate );\n}\n\nanimate();\n\n"
  },
  {
    "path": "demos/fullscreen/index.html",
    "content": "<!doctype html>\n<html lang=\"en\">\n<head>\n  <meta charset=\"utf-8\" />\n  <meta name=\"viewport\" content=\"width=device-width\" />\n\n  <title>fullscreen</title>\n\n  <style>\n    body {\n      margin: 0;\n      padding: 0;\n    }\n\n    .illo {\n      display: block;\n      width: 100%;\n      height: 100%;\n      cursor: move;\n      background: #FDB;\n    }\n  </style>\n\n</head>\n<body>\n\n<canvas class=\"illo\"></canvas>\n\n<script src=\"../../js/boilerplate.js\"></script>\n<script src=\"../../js/canvas-renderer.js\"></script>\n<script src=\"../../js/svg-renderer.js\"></script>\n<script src=\"../../js/vector.js\"></script>\n<script src=\"../../js/anchor.js\"></script>\n<script src=\"../../js/path-command.js\"></script>\n<script src=\"../../js/shape.js\"></script>\n<script src=\"../../js/ellipse.js\"></script>\n<script src=\"../../js/rect.js\"></script>\n<script src=\"../../js/group.js\"></script>\n<script src=\"../../js/dragger.js\"></script>\n<script src=\"../../js/illustration.js\"></script>\n<script src=\"fullscreen.js\"></script>\n\n</body>\n</html>\n"
  },
  {
    "path": "demos/hello-world-canvas/hello-world-canvas.js",
    "content": "// ----- variables ----- //\n\nvar isSpinning = true;\n\n// ----- model ----- //\n\nvar illo = new Zdog.Illustration({\n  element: '.illo',\n  zoom: 5,\n  dragRotate: true,\n  onDragStart: function() {\n    isSpinning = false;\n  },\n});\n\n// circle\nnew Zdog.Ellipse({\n  addTo: illo,\n  diameter: 20,\n  translate: { z: 10 },\n  stroke: 5,\n  color: '#636',\n});\n\n// square\nnew Zdog.Rect({\n  addTo: illo,\n  width: 20,\n  height: 20,\n  translate: { z: -10 },\n  stroke: 3,\n  color: '#E62',\n  fill: true,\n});\n\n// ----- animate ----- //\n\nfunction animate() {\n  illo.rotate.y += isSpinning ? 0.03 : 0;\n  illo.updateRenderGraph();\n  requestAnimationFrame( animate );\n}\n\nanimate();\n"
  },
  {
    "path": "demos/hello-world-canvas/index.html",
    "content": "<!doctype html>\n<html lang=\"en\">\n<head>\n  <meta charset=\"utf-8\" />\n  <meta name=\"viewport\" content=\"width=device-width\" />\n\n  <title>Hello world canvas</title>\n\n  <style>\n    html { height: 100%; }\n\n    body {\n      min-height: 100%;\n      margin: 0;\n      display: flex;\n      align-items: center;\n      justify-content: center;\n      font-family: sans-serif;\n      text-align: center;\n    }\n\n    .illo {\n      display: block;\n      margin: 20px auto;\n      background: #FDB;\n      cursor: move;\n    }\n  </style>\n\n</head>\n<body>\n\n<div class=\"container\">\n  <h1>Hello world canvas</h1>\n  <canvas class=\"illo\" width=\"300\" height=\"300\"></canvas>\n</div>\n\n<script src=\"../../js/boilerplate.js\"></script>\n<script src=\"../../js/canvas-renderer.js\"></script>\n<script src=\"../../js/vector.js\"></script>\n<script src=\"../../js/anchor.js\"></script>\n<script src=\"../../js/path-command.js\"></script>\n<script src=\"../../js/shape.js\"></script>\n<script src=\"../../js/ellipse.js\"></script>\n<script src=\"../../js/rect.js\"></script>\n<script src=\"../../js/dragger.js\"></script>\n<script src=\"../../js/illustration.js\"></script>\n<script src=\"hello-world-canvas.js\"></script>\n\n</body>\n</html>\n"
  },
  {
    "path": "demos/hello-world-svg/hello-world-svg.js",
    "content": "// ----- variables ----- //\n\nvar isSpinning = true;\n\n// ----- model ----- //\n\nvar illo = new Zdog.Illustration({\n  element: '.illo',\n  zoom: 5,\n  dragRotate: true,\n  onDragStart: function() {\n    isSpinning = false;\n  },\n});\n\n// circle\nnew Zdog.Ellipse({\n  addTo: illo,\n  diameter: 20,\n  translate: { z: 10 },\n  stroke: 5,\n  color: '#636',\n});\n\n// square\nnew Zdog.Rect({\n  addTo: illo,\n  width: 20,\n  height: 20,\n  translate: { z: -10 },\n  stroke: 3,\n  color: '#E62',\n  fill: true,\n});\n\n// ----- animate ----- //\n\nfunction animate() {\n  illo.rotate.y += isSpinning ? 0.03 : 0;\n  illo.updateRenderGraph();\n  requestAnimationFrame( animate );\n}\n\nanimate();\n"
  },
  {
    "path": "demos/hello-world-svg/index.html",
    "content": "<!doctype html>\n<html lang=\"en\">\n<head>\n  <meta charset=\"utf-8\" />\n  <meta name=\"viewport\" content=\"width=device-width\" />\n\n  <title>Hello world SVG</title>\n\n  <style>\n    html { height: 100%; }\n\n    body {\n      min-height: 100%;\n      margin: 0;\n      display: flex;\n      align-items: center;\n      justify-content: center;\n      font-family: sans-serif;\n      text-align: center;\n    }\n\n    .illo {\n      display: block;\n      margin: 20px auto;\n      background: #FDB;\n      cursor: move;\n    }\n  </style>\n\n</head>\n<body>\n\n<div class=\"container\">\n  <h1>Hello world SVG</h1>\n  <svg class=\"illo\" width=\"300\" height=\"300\"></svg>\n</div>\n\n<script src=\"../../js/boilerplate.js\"></script>\n<script src=\"../../js/svg-renderer.js\"></script>\n<script src=\"../../js/vector.js\"></script>\n<script src=\"../../js/anchor.js\"></script>\n<script src=\"../../js/path-command.js\"></script>\n<script src=\"../../js/shape.js\"></script>\n<script src=\"../../js/ellipse.js\"></script>\n<script src=\"../../js/rect.js\"></script>\n<script src=\"../../js/dragger.js\"></script>\n<script src=\"../../js/illustration.js\"></script>\n<script src=\"hello-world-svg.js\"></script>\n\n</body>\n</html>\n"
  },
  {
    "path": "demos/hemisphere-cone-ball/hemisphere-cone-ball.js",
    "content": "// ----- setup ----- //\n\nvar sceneSize = 48;\nvar isSpinning = true;\nvar TAU = Zdog.TAU;\n\nvar illo = new Zdog.Illustration({\n  element: '.illo',\n  dragRotate: true,\n  resize: 'fullscreen',\n  onDragStart: function() {\n    isSpinning = false;\n  },\n  onResize: function( width, height ) {\n    this.zoom = Math.floor( Math.min( width, height ) / sceneSize );\n  },\n});\n\n// colors\nvar yellow = '#ED0';\nvar gold = '#EA0';\nvar orange = '#E62';\nvar garnet = '#C25';\nvar eggplant = '#636';\n\n// ----- model ----- //\n\nvar hemi = new Zdog.Hemisphere({\n  addTo: illo,\n  diameter: 13,\n  translate: { y: -16 },\n  rotate: { x: -TAU/4 },\n  color: garnet,\n  backface: eggplant,\n  stroke: false,\n});\nvar cone = new Zdog.Cone({\n  addTo: illo,\n  diameter: 13,\n  length: 6.5,\n  translate: { y: 16 },\n  rotate: { x: TAU/4 },\n  color: garnet,\n  backface: eggplant,\n  stroke: false,\n});\n\nvar colorWheel = [ eggplant, garnet, orange, gold, yellow ];\n\n[ true, false ].forEach( function( isHemi ) {\n  var shape = isHemi ? hemi : cone;\n\n  for ( var i = 0; i < 5; i++ ) {\n    var rotor1 = new Zdog.Anchor({\n      addTo: illo,\n      rotate: { y: TAU/5 * i },\n    });\n    var rotor2 = new Zdog.Anchor({\n      addTo: rotor1,\n      rotate: { x: TAU/6 },\n    });\n\n    shape.copy({\n      addTo: rotor2,\n      color: colorWheel[i],\n      backface: colorWheel[ ( i + 7 ) % 5 ],\n    });\n  }\n} );\n\n// ----- animate ----- //\n\nvar keyframes = [\n  { x: TAU * 0, y: TAU * 0 },\n  { x: TAU/2, y: TAU/2 },\n  { x: TAU * 1, y: TAU * 1 },\n];\n\nvar ticker = 0;\nvar cycleCount = 180;\nvar turnLimit = keyframes.length - 1;\n\nfunction animate() {\n  spin();\n  illo.updateRenderGraph();\n  requestAnimationFrame( animate );\n}\n\nfunction spin() {\n  if ( !isSpinning ) {\n    return;\n  }\n  var progress = ticker/cycleCount;\n  var tween = Zdog.easeInOut( progress % 1, 3 );\n  var turn = Math.floor( progress % turnLimit );\n  var keyA = keyframes[ turn ];\n  var keyB = keyframes[ turn + 1 ];\n  var thetaX = Zdog.lerp( keyA.x, keyB.x, tween );\n  illo.rotate.x = Math.cos( thetaX ) * TAU/12;\n  illo.rotate.y = Zdog.lerp( keyA.y, keyB.y, tween );\n  ticker++;\n}\n\nanimate();\n"
  },
  {
    "path": "demos/hemisphere-cone-ball/index.html",
    "content": "<!doctype html>\n<html lang=\"en\">\n<head>\n  <meta charset=\"utf-8\" />\n  <meta name=\"viewport\" content=\"width=device-width\" />\n\n  <title>hemisphere-cone-ball</title>\n\n  <style>\n    html { height: 100%; }\n\n    body {\n      min-height: 100%;\n      margin: 0;\n    }\n\n    .illo {\n      display: block;\n      width: 100%;\n      height: 100%;\n      background: #FDB;\n      cursor: move;\n    }\n  </style>\n\n</head>\n<body>\n\n<canvas class=\"illo\"></canvas>\n\n<script src=\"../../js/boilerplate.js\"></script>\n<script src=\"../../js/canvas-renderer.js\"></script>\n<script src=\"../../js/vector.js\"></script>\n<script src=\"../../js/anchor.js\"></script>\n<script src=\"../../js/path-command.js\"></script>\n<script src=\"../../js/shape.js\"></script>\n<script src=\"../../js/group.js\"></script>\n<script src=\"../../js/ellipse.js\"></script>\n<script src=\"../../js/hemisphere.js\"></script>\n<script src=\"../../js/cone.js\"></script>\n<script src=\"../../js/dragger.js\"></script>\n<script src=\"../../js/illustration.js\"></script>\n<script src=\"hemisphere-cone-ball.js\"></script>\n\n</body>\n</html>\n"
  },
  {
    "path": "demos/houses/houses.js",
    "content": "// ----- setup ----- //\n\nvar sceneSize = 56;\nvar isSpinning = true;\nvar TAU = Zdog.TAU;\n\nvar offWhite = '#FED';\nvar yellow = '#ED0';\nvar gold = '#EA0';\nvar orange = '#E62';\nvar garnet = '#C25';\nvar eggplant = '#636';\n\n// enable fill, disable stroke for all defaults\n[ Zdog.Rect, Zdog.Shape, Zdog.Ellipse ].forEach( function( Item ) {\n  Item.defaults.fill = true;\n  Item.defaults.stroke = false;\n} );\n\nvar initRotate = { y: TAU/8 };\nvar turnRatio = 1 / Math.sin( TAU/8 );\n\nvar illo = new Zdog.Illustration({\n  element: '.illo',\n  rotate: initRotate,\n  // stretch looks circular at 1/8 turn\n  scale: { x: turnRatio, z: turnRatio },\n  dragRotate: true,\n  resize: 'fullscreen',\n  onDragStart: function() {\n    isSpinning = false;\n  },\n  onResize: function( width, height ) {\n    this.zoom = Math.floor( Math.min( width, height ) / sceneSize );\n  },\n});\n\n// ----- model ----- //\n\nvar house = new Zdog.Anchor({\n  addTo: illo,\n  translate: { x: -2, y: 2, z: 8 },\n});\n\nvar frontGroup = new Zdog.Group({\n  addTo: house,\n  translate: { z: 5 },\n});\n// front wall\nnew Zdog.Rect({\n  addTo: frontGroup,\n  width: 14,\n  height: 14,\n  color: garnet,\n});\n\nvar frontWindow = new Zdog.Rect({\n  addTo: frontGroup,\n  width: 2,\n  height: 4,\n  translate: { x: -4, y: -3 },\n  color: eggplant,\n});\nfrontWindow.copy({\n  translate: { y: -3 },\n});\nfrontWindow.copy({\n  translate: { x: 4, y: -3 },\n});\nfrontWindow.copy({\n  translate: { x: -4, y: 3 },\n});\n// door\nnew Zdog.Shape({\n  addTo: frontGroup,\n  path: [\n    { x: -2, y: 3 },\n    { x: -2, y: -1 },\n    { arc: [\n      { x: -2, y: -3 },\n      { x: 0, y: -3 },\n    ] },\n    { arc: [\n      { x: 2, y: -3 },\n      { x: 2, y: -1 },\n    ] },\n    { x: 2, y: 3 },\n  ],\n  translate: { x: 2, y: 4 },\n  color: eggplant,\n});\n\n// backWall\nvar backGroup = frontGroup.copyGraph({\n  translate: { z: -5 },\n  rotate: { y: TAU/2 },\n});\n\nbackGroup.children.forEach( function( child, i ) {\n  // orange windows, yellow wall\n  child.color = i ? orange : yellow;\n} );\n\nvar rightGroup = new Zdog.Group({\n  addTo: house,\n  translate: { x: 7 },\n  rotate: { y: -TAU/4 },\n});\n// right wall\nnew Zdog.Shape({\n  addTo: rightGroup,\n  path: [\n    { x: -5, y:  7 },\n    { x: -5, y: -7 },\n    { x:  0, y: -12 },\n    { x:  5, y: -7 },\n    { x:  5, y:  7 },\n  ],\n  width: 10,\n  height: 14,\n  color: offWhite,\n});\n\nvar sideWindow = frontWindow.copy({\n  addTo: rightGroup,\n  translate: { x: -2, y: -3 },\n  color: gold,\n});\nsideWindow.copy({\n  translate: { x:  2, y: -3 },\n});\nsideWindow.copy({\n  translate: { x:  2, y:  3 },\n});\nsideWindow.copy({\n  translate: { x: -2, y:  3 },\n});\n\n// porthole\nnew Zdog.Ellipse({\n  addTo: rightGroup,\n  width: 2,\n  height: 2,\n  translate: { y: -8 },\n  color: gold,\n});\n\nvar leftGroup = rightGroup.copyGraph({\n  translate: { x: -7 },\n  rotate: { y: TAU/4 },\n});\n\nleftGroup.children.forEach( function( child, i ) {\n  // eggplant windows, yellow wall\n  child.color = i ? eggplant : orange;\n} );\n\n// front roof\nvar frontRoof = new Zdog.Shape({\n  addTo: house,\n  path: [\n    { x: -7, y:  -7, z: 5 },\n    { x: -7, y: -12, z: 0 },\n    { x:  7, y: -12, z: 0 },\n    { x:  7, y:  -7, z: 5 },\n  ],\n  color: eggplant,\n});\n\nfrontRoof.copy({\n  scale: { z: -1 },\n  color: garnet,\n});\n\n// floor\nnew Zdog.Rect({\n  addTo: house,\n  width: 14,\n  height: 10,\n  translate: { y: 7 },\n  rotate: { x: TAU/4 },\n  color: eggplant,\n});\n\nhouse.copyGraph({\n  translate: house.translate.copy().multiply( -1 ),\n});\n\n// ----- animate ----- //\n\nvar ticker = 0;\nvar cycleCount = 240;\n\nfunction animate() {\n  if ( isSpinning ) {\n    var progress = ticker/cycleCount;\n    var tween = Zdog.easeInOut( progress % 1, 3 );\n    illo.rotate.y = tween * TAU + initRotate.y;\n    ticker++;\n  }\n  illo.updateRenderGraph();\n  requestAnimationFrame( animate );\n}\n\nanimate();\n\n"
  },
  {
    "path": "demos/houses/index.html",
    "content": "<!doctype html>\n<html lang=\"en\">\n<head>\n  <meta charset=\"utf-8\" />\n  <meta name=\"viewport\" content=\"width=device-width\" />\n\n  <title>houses</title>\n\n  <style>\n    html { height: 100%; }\n\n    body {\n      min-height: 100%;\n      margin: 0;\n      display: flex;\n      align-items: center;\n      justify-content: center;\n      background: white;\n      font-family: sans-serif;\n      text-align: center;\n    }\n\n    .illo {\n      display: block;\n      width: 100%;\n      height: 100%;\n      background: #FDB;\n      cursor: move;\n    }\n  </style>\n\n</head>\n<body>\n\n<div class=\"container\">\n  <canvas class=\"illo\"></canvas>\n</div>\n\n<script src=\"../../js/boilerplate.js\"></script>\n<script src=\"../../js/canvas-renderer.js\"></script>\n<script src=\"../../js/svg-renderer.js\"></script>\n<script src=\"../../js/vector.js\"></script>\n<script src=\"../../js/anchor.js\"></script>\n<script src=\"../../js/path-command.js\"></script>\n<script src=\"../../js/shape.js\"></script>\n<script src=\"../../js/ellipse.js\"></script>\n<script src=\"../../js/rect.js\"></script>\n<script src=\"../../js/group.js\"></script>\n<script src=\"../../js/dragger.js\"></script>\n<script src=\"../../js/illustration.js\"></script>\n<script src=\"houses.js\"></script>\n\n</body>\n</html>\n"
  },
  {
    "path": "demos/kid-kit/index.html",
    "content": "<!doctype html>\n<html lang=\"en\">\n<head>\n  <meta charset=\"utf-8\" />\n  <meta name=\"viewport\" content=\"width=device-width\" />\n\n  <title>Kid Kit</title>\n\n  <style>\n    html { height: 100%; }\n\n    body {\n      min-height: 100%;\n      margin: 0;\n      display: flex;\n      align-items: center;\n      justify-content: center;\n      font-family: sans-serif;\n      text-align: center;\n    }\n\n    .illo {\n      display: block;\n      margin: 20px auto;\n      background: #FDB;\n      cursor: move;\n    }\n  </style>\n\n</head>\n<body>\n\n<div class=\"container\">\n  <h1>Kid Kit</h1>\n  <canvas class=\"illo\" width=\"300\" height=\"300\"></canvas>\n</div>\n\n<script src=\"../../js/boilerplate.js\"></script>\n<script src=\"../../js/canvas-renderer.js\"></script>\n<script src=\"../../js/vector.js\"></script>\n<script src=\"../../js/anchor.js\"></script>\n<script src=\"../../js/path-command.js\"></script>\n<script src=\"../../js/shape.js\"></script>\n<script src=\"../../js/dragger.js\"></script>\n<script src=\"../../js/illustration.js\"></script>\n<script src=\"kid-kit.js\"></script>\n\n</body>\n</html>\n"
  },
  {
    "path": "demos/kid-kit/kid-kit.js",
    "content": "// ----- setup ----- //\n\nvar offWhite = '#FED';\nvar gold = '#EA0';\nvar garnet = '#C25';\nvar eggplant = '#636';\n\nvar illo = new Zdog.Illustration({\n  element: '.illo',\n  zoom: 4,\n  dragRotate: true,\n});\n\n// ----- model ----- //\n\n// body center\nnew Zdog.Shape({\n  path: [\n    { x: -3, y: 10 },\n    { x: 0, y: 14 },\n    { x: 3, y: 10 },\n  ],\n  addTo: illo,\n  color: offWhite,\n  stroke: 13,\n});\n\n// head circle\nnew Zdog.Shape({\n  addTo: illo,\n  translate: { y: -12 },\n  color: gold,\n  stroke: 32,\n});\n\n// nose\nvar nose = new Zdog.Anchor({\n  addTo: illo,\n  translate: { y: -7, z: 17 },\n});\nnew Zdog.Shape({\n  path: [\n    { x: -1 },\n    { x: 1 },\n  ],\n  addTo: nose,\n  color: eggplant,\n  stroke: 3,\n});\nnew Zdog.Shape({\n  path: [\n    { y: 0 },\n    { y: 1 },\n  ],\n  addTo: nose,\n  color: eggplant,\n  stroke: 3,\n});\n\n// snout\nnew Zdog.Shape({\n  path: [\n    { x: -2, y: -5, z: 11 },\n    { x:  2, y: -5, z: 11 },\n    { x:  2, y: -3, z: 7 },\n    { x: -2, y: -3, z: 7 },\n  ],\n  addTo: illo,\n  color: gold,\n  stroke: 12,\n});\n\n// eyes\nvar eye = new Zdog.Shape({\n  path: [\n    { y: -12 },\n    { y: -9 },\n  ],\n  addTo: illo,\n  translate: { x: -8, z: 11 },\n  color: eggplant,\n  stroke: 4,\n});\neye.copy({\n  translate: { x: 8, z: 11 },\n});\n\n// ears\nvar frontEarZ = 4;\nvar topEarY = -30;\nvar earColor = gold;\n\nvar earAnchor = new Zdog.Anchor({\n  addTo: illo,\n  translate: { y: topEarY, z: frontEarZ },\n});\n\nvar earA = { x: 14, y: 12, z: -4 };\nvar earB = { x: 14, y: 0, z: 0 };\nvar earC = { x: 7, y: 11, z: -14 };\nvar earD = { x: 10, y: 0, z: 0 };\nvar earE = { x: 3, y: 5, z: 0 };\n// outer ear\nnew Zdog.Shape({\n  path: [ earA, earB, earC ],\n  addTo: earAnchor,\n  color: earColor,\n  fill: true,\n  stroke: 4,\n});\nnew Zdog.Shape({\n  path: [ earB, earC, earD ],\n  addTo: earAnchor,\n  color: earColor,\n  fill: true,\n  stroke: 4,\n});\nnew Zdog.Shape({\n  path: [ earC, earD, earE ],\n  addTo: earAnchor,\n  color: earColor,\n  fill: true,\n  stroke: 4,\n});\n// inner ear\nvar innerEarXShift = 4;\nnew Zdog.Shape({\n  path: [\n    { x: earA.x - innerEarXShift, y: earA.y - 3 },\n    { x: earD.x, y: earD.y + 5 },\n    { x: earE.x + innerEarXShift, y: earE.y + 2 },\n  ],\n  addTo: earAnchor,\n  color: offWhite,\n  fill: true,\n  stroke: 3,\n});\n\nearAnchor.copyGraph({\n  scale: { x: -1 },\n});\n\n// var whiskerX0 = 10*xSide;\n// var whiskerX1 = 17*xSide;\n// var whiskerY0 = -6+yShift;\n// var whiskerY1 = -2+yShift;\n\n// whiskers\nvar whisker = new Zdog.Shape({\n  path: [\n    { x: 10, y: -6 },\n    { x: 10, y: -2 },\n    { x: 17, y: -2 },\n  ],\n  addTo: illo,\n  translate: { z: 6 },\n  fill: true,\n  color: gold,\n  stroke: 3,\n});\nwhisker.copy({\n  translate: { y: -6, z: 6 },\n});\nwhisker.copy({\n  scale: { x: -1 },\n});\nwhisker.copy({\n  scale: { x: -1 },\n  translate: { y: -6, z: 6 },\n});\n\n// arms\n\nvar armAnchor = new Zdog.Anchor({\n  addTo: illo,\n});\n\n// shoulder\nnew Zdog.Shape({\n  path: [\n    { x: 11, y: 6, z: -2 },\n    { x: 12, y: 9, z: -2.5 },\n  ],\n  addTo: armAnchor,\n  closed: true,\n  color: eggplant,\n  stroke: 8,\n});\n// forearm\nnew Zdog.Shape({\n  path: [\n    { x: 12, y: 12, z: -2.5 },\n    { x: 12, y: 15, z: -2 },\n  ],\n  addTo: armAnchor,\n  color: gold,\n  stroke: 8,\n});\n// hand\nnew Zdog.Shape({\n  path: [ { x: 11, y: 18, z: -1 } ],\n  addTo: armAnchor,\n  color: eggplant,\n  stroke: 10,\n});\n\narmAnchor.copyGraph({\n  scale: { x: -1 },\n});\n\n// legs\nvar leg = new Zdog.Shape({\n  path: [\n    { y: 20 },\n    { y: 27 },\n  ],\n  addTo: illo,\n  translate: { x: -6 },\n  color: eggplant,\n  stroke: 8,\n});\nleg.copy({\n  translate: { x: 6 },\n});\n\nvar cloakX0 = 8;\nvar cloakX1 = 5;\n\nvar cloakY0 = 4;\nvar cloakY1 = 6;\nvar cloakY2 = 13;\nvar cloakY3 = 21;\n\nvar cloakZ0 = 0;\nvar cloakZ1 = 6;\nvar cloakZ2 = 8;\n\nvar cloakSide = new Zdog.Anchor({\n  addTo: illo,\n});\n\n// top straps\nvar topCloakStrap = new Zdog.Shape({\n  path: [\n    { x: cloakX0, y: cloakY0, z: cloakZ0 },\n    { x: cloakX0, y: cloakY1, z: cloakZ1 },\n    { x: cloakX1, y: cloakY1, z: cloakZ1 },\n  ],\n  addTo: cloakSide,\n  fill: true,\n  color: garnet,\n  stroke: 4,\n});\n\ntopCloakStrap.copy({\n  scale: { x: -1 },\n});\n\nvar vNeckY = ( cloakY1 + cloakY2 )/2;\nvar vNeckZ = ( cloakZ2 + cloakZ1 )/2;\nnew Zdog.Shape({\n  path: [\n    { x: -cloakX0, y: cloakY1, z: cloakZ1 },\n    { x: -cloakX1, y: cloakY1, z: cloakZ1 },\n    { x: 0, y: vNeckY, z: vNeckZ },\n    { x: cloakX1, y: cloakY1, z: cloakZ1 },\n    { x: cloakX0, y: cloakY1, z: cloakZ1 },\n    { x: cloakX0, y: cloakY2, z: cloakZ2 },\n    { x: -cloakX0, y: cloakY2, z: cloakZ2 },\n  ],\n  addTo: cloakSide,\n  fill: true,\n  color: garnet,\n  stroke: 4,\n});\nnew Zdog.Shape({\n  path: [\n    { x: -cloakX0, y: cloakY2 },\n    { x: cloakX0, y: cloakY2 },\n    { x: cloakX0, y: cloakY3 },\n    { x: -cloakX0, y: cloakY3 },\n  ],\n  addTo: cloakSide,\n  translate: { z: cloakZ2 },\n  fill: true,\n  color: garnet,\n  stroke: 4,\n});\n\ncloakSide.copyGraph({\n  scale: { z: -1 },\n});\n\n// ----- animate ----- //\n\nfunction animate() {\n  illo.updateRenderGraph();\n  requestAnimationFrame( animate );\n}\n\nanimate();\n\n"
  },
  {
    "path": "demos/kirby-parasol/index.html",
    "content": "<!doctype html>\n<html lang=\"en\">\n<head>\n  <meta charset=\"utf-8\" />\n  <meta name=\"viewport\" content=\"width=device-width\" />\n\n  <title>kirby parasol</title>\n\n  <style>\n    html { height: 100%; }\n\n    body {\n      min-height: 100%;\n      margin: 0;\n      background: #425;\n      color: white;\n    }\n\n    .illo {\n      display: block;\n      width: 100%;\n      height: 100%;\n      cursor: move;\n    }\n  </style>\n\n</head>\n<body>\n\n<canvas class=\"illo\"></canvas>\n\n<script src=\"../../js/boilerplate.js\"></script>\n<script src=\"../../js/canvas-renderer.js\"></script>\n<script src=\"../../js/vector.js\"></script>\n<script src=\"../../js/path-command.js\"></script>\n<script src=\"../../js/anchor.js\"></script>\n<script src=\"../../js/shape.js\"></script>\n<script src=\"../../js/ellipse.js\"></script>\n<script src=\"../../js/group.js\"></script>\n<script src=\"../../js/dragger.js\"></script>\n<script src=\"../../js/illustration.js\"></script>\n<script src=\"kirby-parasol.js\"></script>\n\n</body>\n</html>\n"
  },
  {
    "path": "demos/kirby-parasol/kirby-parasol.js",
    "content": "// ----- setup ----- //\n\nvar sceneSize = 80;\nvar isSpinning = true;\nvar TAU = Zdog.TAU;\n// colors\nvar pink = '#F8B';\nvar blush = '#F5A';\nvar black = '#333';\nvar shoe = '#D03';\nvar red = '#E10';\nvar yellow = '#FD0';\n\nvar illo = new Zdog.Illustration({\n  element: '.illo',\n  dragRotate: true,\n  resize: 'fullscreen',\n  onDragStart: function() {\n    isSpinning = false;\n  },\n  onResize: function( width, height ) {\n    this.zoom = Math.floor( Math.min( width, height ) / sceneSize );\n  },\n});\n\n// ----- model ----- //\n\nvar body = new Zdog.Shape({\n  stroke: 22,\n  translate: { y: 11 },\n  rotate: { x: 0.3, z: 0.1 },\n  addTo: illo,\n  color: pink,\n});\n\nvar face = new Zdog.Anchor({\n  translate: { z: 10.5 },\n  addTo: body,\n});\n\n[ -1, 1 ].forEach( function( xSide ) {\n  var eyeGroup = new Zdog.Group({\n    addTo: face,\n    translate: { x: 2.4 * xSide, y: -2 },\n    rotate: { x: -0.1 },\n  });\n  // eye\n  new Zdog.Ellipse({\n    width: 1.4,\n    height: 5.5,\n    addTo: eyeGroup,\n    stroke: 1,\n    color: black,\n    fill: true,\n  });\n  // eye highlight\n  new Zdog.Ellipse({\n    width: 1,\n    height: 2,\n    addTo: eyeGroup,\n    translate: { y: -1.5, z: 0.5 },\n    stroke: 0.5,\n    color: '#FFF',\n    fill: true,\n  });\n\n  // cheek holder\n  var cheekHolder = new Zdog.Anchor({\n    addTo: body,\n    rotate: { y: 0.6 * xSide },\n  });\n\n  new Zdog.Ellipse({\n    width: 2.5,\n    height: 1,\n    translate: { y: 1, z: 10.5 },\n    addTo: cheekHolder,\n    color: blush,\n    stroke: 1,\n  });\n\n} );\n\n// mouth\nnew Zdog.Shape({\n  path: [\n    { x: 0, y: 0 },\n    { bezier: [\n      { x: 1.1, y: 0 },\n      { x: 1.1, y: 0.2 },\n      { x: 1.1, y: 0.5 },\n    ] },\n    { bezier: [\n      { x: 1.1, y: 1.1 },\n      { x: 0.2, y: 1.8 },\n      { x: 0, y: 1.8 },\n    ] },\n    { bezier: [\n      { x: -0.2, y: 1.8 },\n      { x: -1.1, y: 1.1 },\n      { x: -1.1, y: 0.5 },\n    ] },\n    { bezier: [\n      { x: -1.1, y: 0.2 },\n      { x: -1.1, y: 0 },\n      { x: 0, y: 0 },\n    ] },\n  ],\n  addTo: face,\n  translate: { y: 2, z: -0.5 },\n  stroke: 1,\n  color: shoe,\n  fill: true,\n});\n\nvar rightArm = new Zdog.Shape({\n  path: [\n    { y: 0 },\n    { y: -7 },\n  ],\n  addTo: body,\n  translate: { x: -6, y: -4, z: 0 },\n  color: pink,\n  stroke: 7,\n});\n\n// left arm\nrightArm.copy({\n  path: [\n    { x: 0 },\n    { x: 6 },\n  ],\n  translate: { x: 6, y: -2, z: 0 },\n});\n\n// right foot\nvar rightFoot = new Zdog.Shape({\n  path: [\n    { x: 0, y: -2 },\n    { arc: [\n      { x: 2, y: -2 },\n      { x: 2, y: 0 },\n    ] },\n    { arc: [\n      { x: 2, y: 5 },\n      { x: 0, y: 5 },\n    ] },\n    { arc: [\n      { x: -2, y: 5 },\n      { x: -2, y: 0 },\n    ] },\n    { arc: [\n      { x: -2, y: -2 },\n      { x: 0, y: -2 },\n    ] },\n  ],\n  addTo: body,\n  translate: { x: -1, y: 9, z: -9 },\n  rotate: { z: 0.2 },\n  stroke: 6,\n  color: shoe,\n  fill: true,\n  closed: false,\n});\n\nrightFoot.copy({\n  translate: { x: 9.5, y: 6, z: -6 },\n  rotate: { z: -1.1, y: 0.8 },\n});\n\n// ----- umbrella ----- //\n\n// umbrella rod\nvar umbrella = new Zdog.Shape({\n  path: [\n    { y: 0 },\n    { y: 22 },\n  ],\n  addTo: rightArm,\n  translate: { y: -33, z: 2 },\n  rotate: { y: 0.5 },\n  color: yellow,\n  stroke: 1,\n});\n\n// star\nvar starPath = ( function() {\n  var path = [];\n  var starRadiusA = 3;\n  var starRadiusB = 1.7;\n  for ( var i = 0; i < 10; i++ ) {\n    var radius = i % 2 ? starRadiusA : starRadiusB;\n    var angle = TAU * i/10 + TAU/4;\n    var point = {\n      x: Math.cos( angle ) * radius,\n      y: Math.sin( angle ) * radius,\n    };\n    path.push( point );\n  }\n  return path;\n} )();\n// star shape\nvar star = new Zdog.Shape({\n  path: starPath,\n  addTo: umbrella,\n  translate: { y: -4.5 },\n  stroke: 2,\n  color: yellow,\n  fill: true,\n});\n\n// umbrella handle\nnew Zdog.Shape({\n  path: [\n    { z: 0, y: 0 },\n    { z: 0, y: 1 },\n    { arc: [\n      { z: 0, y: 4 },\n      { z: 3, y: 4 },\n    ] },\n    { arc: [\n      { z: 6, y: 4 },\n      { z: 6, y: 1 },\n    ] },\n  ],\n  addTo: umbrella,\n  translate: { y: 23 },\n  stroke: 2,\n  color: '#37F',\n  closed: false,\n});\n\n// umbrella shield panels\n( function() {\n  var umbPanelX = 14 * Math.sin( TAU/24 );\n  var umbPanelZ = 14 * Math.cos( TAU/24 );\n  for ( var i = 0; i < 12; i++ ) {\n    var colorSide = Math.floor( i/2 ) % 2;\n    new Zdog.Shape({\n      path: [\n        { x: 0, y: 0, z: 0 },\n        { arc: [\n          { x: -umbPanelX, y: 0, z: umbPanelZ },\n          { x: -umbPanelX, y: 14, z: umbPanelZ },\n        ] },\n        { x: umbPanelX, y: 14, z: umbPanelZ },\n        { arc: [\n          { x: umbPanelX, y: 0, z: umbPanelZ },\n          { x: 0, y: 0, z: 0 },\n        ] },\n      ],\n      addTo: umbrella,\n      rotate: { y: TAU/12 * i },\n      stroke: 1,\n      color: colorSide ? red : 'white',\n      fill: true,\n    });\n  }\n} )();\n\n// floater stars\n( function() {\n  for ( var i = 0; i < 6; i++ ) {\n    var starHolder = new Zdog.Anchor({\n      addTo: umbrella,\n      translate: { y: 10 },\n      rotate: { y: TAU/6 * i + TAU/24 },\n    });\n    star.copy({\n      addTo: starHolder,\n      translate: { z: 28 },\n    });\n  }\n} )();\n\n// ----- animate ----- //\n\nfunction animate() {\n  illo.rotate.y += isSpinning ? -0.03 : 0;\n  illo.updateRenderGraph();\n  requestAnimationFrame( animate );\n}\n\nanimate();\n\n"
  },
  {
    "path": "demos/no-illo-canvas/index.html",
    "content": "<!doctype html>\n<html lang=\"en\">\n<head>\n  <meta charset=\"utf-8\" />\n  <meta name=\"viewport\" content=\"width=device-width\" />\n\n  <title>No Illo canvas</title>\n\n  <style>\n    html { height: 100%; }\n\n    body {\n      min-height: 100%;\n      margin: 0;\n      display: flex;\n      align-items: center;\n      justify-content: center;\n      font-family: sans-serif;\n      text-align: center;\n    }\n\n    .zdog-canvas {\n      display: block;\n      margin: 20px auto;\n      background: #FDB;\n      cursor: move;\n    }\n  </style>\n\n</head>\n<body>\n\n<div class=\"container\">\n  <h1>No Illo canvas</h1>\n  <canvas class=\"zdog-canvas\" width=\"300\" height=\"300\"></canvas>\n</div>\n\n<script src=\"../../js/boilerplate.js\"></script>\n<script src=\"../../js/canvas-renderer.js\"></script>\n<script src=\"../../js/vector.js\"></script>\n<script src=\"../../js/anchor.js\"></script>\n<script src=\"../../js/path-command.js\"></script>\n<script src=\"../../js/shape.js\"></script>\n<script src=\"../../js/ellipse.js\"></script>\n<script src=\"../../js/rect.js\"></script>\n<script src=\"../../js/dragger.js\"></script>\n\n<script src=\"no-illo-canvas.js\"></script>\n\n</body>\n</html>\n"
  },
  {
    "path": "demos/no-illo-canvas/no-illo-canvas.js",
    "content": "// ----- setup ----- //\n\n// get canvas element and its context\nvar canvas = document.querySelector('.zdog-canvas');\nvar ctx = canvas.getContext('2d');\n// get canvas size\nvar canvasWidth = canvas.width;\nvar canvasHeight = canvas.height;\n// illustration variables\nvar zoom = 5;\nvar isSpinning = true;\nvar TAU = Zdog.TAU;\n\n// ----- model ----- //\n\nvar scene = new Zdog.Anchor();\n\n// circle\nnew Zdog.Ellipse({\n  addTo: scene,\n  diameter: 20,\n  translate: { z: 10 },\n  stroke: 5,\n  color: '#636',\n});\n\n// square\nnew Zdog.Rect({\n  addTo: scene,\n  width: 20,\n  height: 20,\n  translate: { z: -10 },\n  stroke: 3,\n  color: '#E62',\n  fill: true,\n});\n\n// ----- animate ----- //\n\nfunction animate() {\n  scene.rotate.y += isSpinning ? 0.03 : 0;\n  scene.updateGraph();\n  render();\n  requestAnimationFrame( animate );\n}\n\nfunction render() {\n  // clear canvas\n  ctx.clearRect( 0, 0, canvasWidth, canvasHeight );\n  ctx.save();\n  // center canvas & zoom\n  ctx.translate( canvasWidth/2, canvasHeight/2 );\n  ctx.scale( zoom, zoom );\n  // set lineJoin and lineCap to round\n  ctx.lineJoin = 'round';\n  ctx.lineCap = 'round';\n  // render scene graph\n  scene.renderGraphCanvas( ctx );\n  ctx.restore();\n}\n\nanimate();\n\n// ----- drag ----- //\n\nvar dragStartRX, dragStartRY;\nvar minSize = Math.min( canvasWidth, canvasHeight );\n\n// add drag-rotatation with Dragger\nnew Zdog.Dragger({\n  startElement: canvas,\n  onDragStart: function() {\n    isSpinning = false;\n    dragStartRX = scene.rotate.x;\n    dragStartRY = scene.rotate.y;\n  },\n  onDragMove: function( pointer, moveX, moveY ) {\n    scene.rotate.x = dragStartRX - ( moveY/minSize * TAU );\n    scene.rotate.y = dragStartRY - ( moveX/minSize * TAU );\n  },\n});\n"
  },
  {
    "path": "demos/no-illo-svg/index.html",
    "content": "<!doctype html>\n<html lang=\"en\">\n<head>\n  <meta charset=\"utf-8\" />\n  <meta name=\"viewport\" content=\"width=device-width\" />\n\n  <title>No Illo SVG</title>\n\n  <style>\n    html { height: 100%; }\n\n    body {\n      min-height: 100%;\n      margin: 0;\n      display: flex;\n      align-items: center;\n      justify-content: center;\n      font-family: sans-serif;\n      text-align: center;\n    }\n\n    .zdog-svg {\n      display: block;\n      margin: 20px auto;\n      background: #FDB;\n      cursor: move;\n    }\n  </style>\n\n</head>\n<body>\n\n<div class=\"container\">\n  <h1>No Illo SVG</h1>\n  <svg class=\"zdog-svg\" width=\"300\" height=\"300\"></svg>\n</div>\n\n<script src=\"../../js/boilerplate.js\"></script>\n<script src=\"../../js/svg-renderer.js\"></script>\n<script src=\"../../js/vector.js\"></script>\n<script src=\"../../js/anchor.js\"></script>\n<script src=\"../../js/path-command.js\"></script>\n<script src=\"../../js/shape.js\"></script>\n<script src=\"../../js/ellipse.js\"></script>\n<script src=\"../../js/rect.js\"></script>\n<script src=\"../../js/dragger.js\"></script>\n\n<script src=\"no-illo-svg.js\"></script>\n\n</body>\n</html>\n"
  },
  {
    "path": "demos/no-illo-svg/no-illo-svg.js",
    "content": "// ----- setup ----- //\n\n// svg element\nvar svg = document.querySelector('svg');\n// set size\nvar zoom = 5;\nvar svgWidth = svg.getAttribute('width');\nvar svgHeight = svg.getAttribute('height');\n// set viewBox for zoom & centering\nvar viewWidth = svgWidth/zoom;\nvar viewHeight = svgHeight/zoom;\nsvg.setAttribute( 'viewBox', -viewWidth/2 + ' ' + -viewHeight/2 + ' ' +\n  viewWidth + ' ' + viewHeight );\n// rendering variable\nvar isSpinning = true;\nvar TAU = Zdog.TAU;\n\nvar scene = new Zdog.Anchor();\n\n// ----- model ----- //\n\n// circle\nnew Zdog.Ellipse({\n  addTo: scene,\n  diameter: 20,\n  translate: { z: 10 },\n  stroke: 5,\n  color: '#636',\n});\n\n// square\nnew Zdog.Rect({\n  addTo: scene,\n  width: 20,\n  height: 20,\n  translate: { z: -10 },\n  stroke: 3,\n  color: '#E62',\n  fill: true,\n});\n\n// ----- animate ----- //\n\nfunction animate() {\n  scene.rotate.y += isSpinning ? 0.03 : 0;\n  scene.updateGraph();\n  render();\n  requestAnimationFrame( animate );\n}\n\nfunction render() {\n  empty( svg );\n  scene.renderGraphSvg( svg );\n}\n\nanimate();\n\nfunction empty( element ) {\n  while ( element.firstChild ) {\n    element.removeChild( element.firstChild );\n  }\n}\n\n// ----- drag ----- //\n\nvar dragStartRX, dragStartRY;\nvar minSize = Math.min( svgWidth, svgHeight );\n\n// add drag-rotatation with Dragger\nnew Zdog.Dragger({\n  startElement: svg,\n  onDragStart: function() {\n    isSpinning = false;\n    dragStartRX = scene.rotate.x;\n    dragStartRY = scene.rotate.y;\n  },\n  onDragMove: function( pointer, moveX, moveY ) {\n    scene.rotate.x = dragStartRX - ( moveY/minSize * TAU );\n    scene.rotate.y = dragStartRY - ( moveX/minSize * TAU );\n  },\n});\n"
  },
  {
    "path": "demos/path-commands/index.html",
    "content": "<!doctype html>\n<html lang=\"en\">\n<head>\n  <meta charset=\"utf-8\" />\n  <meta name=\"viewport\" content=\"width=device-width\" />\n\n  <title>Path commands</title>\n\n  <style>\n    html { height: 100%; }\n\n    body {\n      min-height: 100%;\n      margin: 0;\n      display: flex;\n      align-items: center;\n      justify-content: center;\n      font-family: sans-serif;\n      text-align: center;\n    }\n\n    .illo {\n      display: block;\n      margin: 20px auto;\n      background: #FDB;\n      cursor: move;\n    }\n  </style>\n\n</head>\n<body>\n\n<div class=\"container\">\n  <h1>Path commands</h1>\n  <canvas class=\"illo\" width=\"300\" height=\"300\"></canvas>\n</div>\n\n<script src=\"../../js/boilerplate.js\"></script>\n<script src=\"../../js/canvas-renderer.js\"></script>\n<script src=\"../../js/vector.js\"></script>\n<script src=\"../../js/anchor.js\"></script>\n<script src=\"../../js/path-command.js\"></script>\n<script src=\"../../js/shape.js\"></script>\n<script src=\"../../js/dragger.js\"></script>\n<script src=\"../../js/illustration.js\"></script>\n<script src=\"path-commands.js\"></script>\n\n</body>\n</html>\n"
  },
  {
    "path": "demos/path-commands/path-commands.js",
    "content": "// ----- variables ----- //\n\nvar eggplant = '#636';\n\n// ----- model ----- //\n\nvar illo = new Zdog.Illustration({\n  element: '.illo',\n  zoom: 5,\n  dragRotate: true,\n});\n\n// lines\nnew Zdog.Shape({\n  addTo: illo,\n  path: [\n    { x: -6, y: -6 },\n    { x:  6, y: -6 },\n    { x: -6, y:  6 },\n    { x:  6, y:  6 },\n  ],\n  translate: { x: -12, y: -12 },\n  closed: false,\n  color: eggplant,\n  stroke: 2,\n});\n\n// move\nnew Zdog.Shape({\n  addTo: illo,\n  path: [\n    { x: -6, y: -6 },\n    { x:  6, y: -6 },\n    { move: { x: -6, y:  6 } },\n    { x:  6, y:  6 },\n  ],\n  translate: { x: 12, y: -12 },\n  closed: false,\n  color: eggplant,\n  stroke: 2,\n});\n\n// arc\nnew Zdog.Shape({\n  addTo: illo,\n  path: [\n    { x: -6, y: -6 }, // start\n    { arc: [\n      { x:  2, y: -6 }, // corner\n      { x:  2, y:  2 }, // end point\n    ] },\n    { arc: [ // start next arc from last end point\n      { x:  2, y:  6 }, // corner\n      { x:  6, y:  6 }, // end point\n    ] },\n  ],\n  translate: { x: -12, y: 12 },\n  closed: false,\n  color: eggplant,\n  stroke: 2,\n});\n\n// bezier\nnew Zdog.Shape({\n  addTo: illo,\n  path: [\n    { x: -6, y: -6 }, // start\n    { bezier: [\n      { x:  2, y: -6 }, // start control point\n      { x:  2, y:  6 }, // end control point\n      { x:  6, y:  6 }, // end control point\n    ] },\n  ],\n  translate: { x: 12, y: 12 },\n  closed: false,\n  color: eggplant,\n  stroke: 2,\n});\n\n// ----- animate ----- //\n\nfunction animate() {\n  illo.updateRenderGraph();\n  requestAnimationFrame( animate );\n}\n\nanimate();\n"
  },
  {
    "path": "demos/resize/index.html",
    "content": "<!doctype html>\n<html lang=\"en\">\n<head>\n  <meta charset=\"utf-8\" />\n  <meta name=\"viewport\" content=\"width=device-width\" />\n\n  <title>resize</title>\n\n  <style>\n    html { height: 100%; }\n\n    body {\n      min-height: 100%;\n      margin: 0;\n      display: flex;\n      align-items: center;\n      justify-content: center;\n      background: white;\n      font-family: sans-serif;\n      text-align: center;\n    }\n\n    .illo {\n      display: block;\n      cursor: move;\n      background: #FDB;\n      border-radius: 8px;\n      margin: 20px 2%;\n      width: 46%;\n    }\n  </style>\n\n</head>\n<body>\n\n<canvas class=\"illo\"  width=\"200\" height=\"200\"></canvas>\n<svg class=\"illo\" width=\"200\" height=\"200\"></svg>\n\n<script src=\"../../js/boilerplate.js\"></script>\n<script src=\"../../js/canvas-renderer.js\"></script>\n<script src=\"../../js/svg-renderer.js\"></script>\n<script src=\"../../js/vector.js\"></script>\n<script src=\"../../js/anchor.js\"></script>\n<script src=\"../../js/path-command.js\"></script>\n<script src=\"../../js/shape.js\"></script>\n<script src=\"../../js/ellipse.js\"></script>\n<script src=\"../../js/rect.js\"></script>\n<script src=\"../../js/group.js\"></script>\n<script src=\"../../js/dragger.js\"></script>\n<script src=\"../../js/illustration.js\"></script>\n<script src=\"resize.js\"></script>\n\n</body>\n</html>\n"
  },
  {
    "path": "demos/resize/resize.js",
    "content": "// ----- setup ----- //\n\nvar zoom = 4;\nvar isSpinning = true;\nvar gold = '#EA0';\nvar orange = '#E62';\nvar garnet = '#C25';\nvar eggplant = '#636';\n\nvar model = new Zdog.Anchor();\n\nvar canvasIllo = new Zdog.Illustration({\n  element: 'canvas',\n  zoom: zoom,\n  resize: true,\n  dragRotate: model,\n  onDragStart: function() {\n    isSpinning = false;\n  },\n  onResize: function( width, height ) {\n    this.zoom = Math.min( width, height ) / 50;\n  },\n});\n\nvar svgIllo = new Zdog.Illustration({\n  element: 'svg',\n  zoom: zoom,\n  resize: true,\n  dragRotate: model,\n  onDragStart: function() {\n    isSpinning = false;\n  },\n  onResize: function( width, height ) {\n    this.zoom = Math.min( width, height ) / 50;\n  },\n});\n\n// HACK set initial zoom for SVG\nsvgIllo.setSize( svgIllo.width, svgIllo.height );\n\n// ----- model ----- //\n\nnew Zdog.Rect({\n  width: 20,\n  height: 20,\n  addTo: model,\n  translate: { z: -10 },\n  stroke: 2,\n  color: garnet,\n});\n\nnew Zdog.Ellipse({\n  diameter: 16,\n  addTo: model,\n  translate: { z: 10 },\n  stroke: 4,\n  color: eggplant,\n});\n\nnew Zdog.Shape({\n  path: [\n    { x:  0, z:  1 },\n    { x: -1, z: -1 },\n    { x:  1, z: -1 },\n  ],\n  scale: { x: 5, z: 5 },\n  addTo: model,\n  stroke: 2,\n  fill: true,\n  color: gold,\n});\n\nnew Zdog.Shape({\n  translate: { x: 10, y: -5 },\n  addTo: model,\n  stroke: 7,\n  color: orange,\n});\n\nmodel.copyGraph({\n  addTo: svgIllo,\n});\n\n// ----- animate ----- //\n\nfunction animate() {\n  model.rotate.y += isSpinning ? 0.03 : 0;\n  model.updateGraph();\n  svgIllo.renderGraph( model );\n  canvasIllo.renderGraph( model );\n  requestAnimationFrame( animate );\n}\n\nanimate();\n\n"
  },
  {
    "path": "demos/shade-and-shades/index.html",
    "content": "<!doctype html>\n<html lang=\"en\">\n<head>\n  <meta charset=\"utf-8\" />\n  <meta name=\"viewport\" content=\"width=device-width\" />\n\n  <title>shade &amp; shades</title>\n\n  <style>\n    html { height: 100%; }\n\n    body {\n      min-height: 100%;\n      margin: 0;\n    }\n\n    .illo {\n      display: block;\n      width: 100%;\n      height: 100%;\n      background: #FDB;\n      cursor: move;\n    }\n  </style>\n\n</head>\n<body>\n\n<canvas class=\"illo\"></canvas>\n\n<script src=\"../../js/boilerplate.js\"></script>\n<script src=\"../../js/canvas-renderer.js\"></script>\n<script src=\"../../js/vector.js\"></script>\n<script src=\"../../js/path-command.js\"></script>\n<script src=\"../../js/anchor.js\"></script>\n<script src=\"../../js/shape.js\"></script>\n<script src=\"../../js/ellipse.js\"></script>\n<script src=\"../../js/dragger.js\"></script>\n<script src=\"../../js/illustration.js\"></script>\n<script src=\"shade-and-shades.js\"></script>\n\n</body>\n</html>\n"
  },
  {
    "path": "demos/shade-and-shades/shade-and-shades.js",
    "content": "// ----- setup ----- //\n\nvar sceneSize = 96;\nvar orange = '#E62';\nvar eggplant = '#636';\n\n// shape defaults\nZdog.Shape.defaults.closed = false;\n[ Zdog.Shape, Zdog.Ellipse ].forEach( function( ShapeClass ) {\n  ShapeClass.defaults.stroke = 3;\n  ShapeClass.defaults.color = orange;\n} );\n\nvar isSpinning = true;\nvar TAU = Zdog.TAU;\nvar initialRotate = { y: -TAU/8 };\n\nvar illo = new Zdog.Illustration({\n  element: '.illo',\n  rotate: initialRotate,\n  dragRotate: true,\n  resize: 'fullscreen',\n  onDragStart: function() {\n    isSpinning = false;\n  },\n  onResize: function( width, height ) {\n    this.zoom = Math.floor( Math.min( width, height ) / sceneSize );\n  },\n});\n\n// ----- model ----- //\n\n// cap top\n[ 0, 1, 2, 3, 4 ].forEach( function( i ) {\n  new Zdog.Shape({\n    path: [\n      { x: -20, y: 4 },\n      { x: -20, y: 0 },\n      { arc: [\n        { x: -20, y: -20 },\n        { x:   0, y: -20 },\n      ] },\n    ],\n    rotate: { y: TAU/6 * i - TAU/12 },\n    addTo: illo,\n  });\n} );\n\n// cap back\nnew Zdog.Ellipse({\n  addTo: illo,\n  diameter: 40,\n  quarters: 2,\n  translate: { y: 4 },\n  rotate: { x: TAU/4, z: -TAU/4 },\n});\n\n// cap back to brim bottom connect\nvar brimConnector = new Zdog.Shape({\n  path: [\n    { x: -20, z: 0 },\n    { arc: [\n      { x: -20, z: 6 },\n      { x: -16, z: 12 },\n    ] },\n  ],\n  addTo: illo,\n  translate: { y: 4 },\n});\n\nbrimConnector.copy({\n  scale: { x: -1 },\n});\n\n// brim back arch\nnew Zdog.Ellipse({\n  addTo: illo,\n  diameter: 32,\n  quarters: 2,\n  translate: { y: 4, z: 12 },\n  rotate: { z: -TAU/4 },\n});\n\nvar brimTip = new Zdog.Vector({ x: 0, y: -12, z: 34 });\nvar brimEdge = brimTip.copy();\nbrimEdge.x = -14;\n\n// brim top line\nnew Zdog.Shape({\n  addTo: illo,\n  path: [\n    { x: 0, y: -12, z: 12 },\n    brimTip,\n  ],\n});\n\nvar brimBridge = new Zdog.Shape({\n  addTo: illo,\n  path: [\n    { x: -16, y: 4, z: 12 },\n    { x: -16, y: 4, z: 18 },\n    { bezier: [\n      { x: -16, y: 4, z: 30 },\n      brimEdge,\n      brimTip,\n    ] },\n  ],\n});\nbrimBridge.copy({\n  scale: { x: -1 },\n});\n\n// glasses front top\nvar glassFront = new Zdog.Shape({\n  addTo: illo,\n  path: [\n    { x: -16 },\n    { x:  16 },\n  ],\n  translate: { y: 8, z: 12 },\n  color: eggplant,\n});\n\n// glass lens\nvar glassLens = new Zdog.Shape({\n  addTo: glassFront,\n  path: [\n    { x: -1, y: -1 },\n    { x:  1, y: -1 },\n    { x:  1, y:  0 },\n    { arc: [\n      { x: 1, y: 1 },\n      { x: 0, y: 1 },\n    ] },\n    { arc: [\n      { x: -1, y: 1 },\n      { x: -1, y: 0 },\n    ] },\n  ],\n  closed: true,\n  scale: 5,\n  translate: { x: -8, y: 5 },\n  color: eggplant,\n  fill: true,\n});\n\nglassLens.copy({\n  translate: { x: 8, y: 5 },\n});\n\n// glasses arm\nvar glassesArm = new Zdog.Shape({\n  addTo: illo,\n  path: [\n    { x: 12, y: 0 },\n    { x: -1, y: 0 },\n    { arc: [\n      { x: -12, y: 0 },\n      { x: -12, y: 8 },\n    ] },\n  ],\n  rotate: { y: TAU/4 },\n  translate: { x: -16, y: 8 },\n  color: eggplant,\n  // only see one arm at time\n  backface: false,\n});\nglassesArm.copy({\n  scale: { x: -1 },\n  rotate: { y: -TAU/4 },\n  translate: { x: 16, y: 8 },\n});\n\n// ----- animate ----- //\n\nvar ticker = 0;\nvar cycleCount = 150;\n\nfunction animate() {\n  if ( isSpinning ) {\n    var progress = ticker/cycleCount;\n    var tween = Zdog.easeInOut( progress % 1, 4 );\n    illo.rotate.y = tween * TAU + initialRotate.y;\n    ticker++;\n  }\n\n  illo.updateRenderGraph();\n  requestAnimationFrame( animate );\n}\n\nanimate();\n"
  },
  {
    "path": "demos/shapes/index.html",
    "content": "<!doctype html>\n<html lang=\"en\">\n<head>\n  <meta charset=\"utf-8\" />\n  <meta name=\"viewport\" content=\"width=device-width\" />\n\n  <title>shapes</title>\n\n  <style>\n    html { height: 100%; }\n\n    body {\n      min-height: 100%;\n      margin: 0;\n    }\n\n    .illo {\n      display: block;\n      width: 100%;\n      height: 100%;\n      background: #FDB;\n      cursor: move;\n    }\n  </style>\n\n</head>\n<body>\n\n<canvas class=\"illo\"></canvas>\n\n<script src=\"../../js/boilerplate.js\"></script>\n<script src=\"../../js/canvas-renderer.js\"></script>\n<script src=\"../../js/svg-renderer.js\"></script>\n<script src=\"../../js/vector.js\"></script>\n<script src=\"../../js/anchor.js\"></script>\n<script src=\"../../js/path-command.js\"></script>\n<script src=\"../../js/shape.js\"></script>\n<script src=\"../../js/ellipse.js\"></script>\n<script src=\"../../js/rect.js\"></script>\n<script src=\"../../js/rounded-rect.js\"></script>\n<script src=\"../../js/polygon.js\"></script>\n<script src=\"../../js/group.js\"></script>\n<script src=\"../../js/hemisphere.js\"></script>\n<script src=\"../../js/cylinder.js\"></script>\n<script src=\"../../js/cone.js\"></script>\n<script src=\"../../js/box.js\"></script>\n<script src=\"../../js/dragger.js\"></script>\n<script src=\"../../js/illustration.js\"></script>\n<script src=\"shapes.js\"></script>\n\n</body>\n</html>\n"
  },
  {
    "path": "demos/shapes/shapes.js",
    "content": "// ----- setup ----- //\n\nvar sceneSize = 24;\nvar isSpinning = true;\nvar TAU = Zdog.TAU;\nvar offWhite = '#FED';\nvar gold = '#EA0';\nvar orange = '#E62';\nvar garnet = '#C25';\nvar eggplant = '#636';\n\nvar illo = new Zdog.Illustration({\n  element: '.illo',\n  dragRotate: true,\n  resize: 'fullscreen',\n  onDragStart: function() {\n    isSpinning = false;\n  },\n  onResize: function( width, height ) {\n    this.zoom = Math.floor( Math.min( width, height ) / sceneSize );\n  },\n});\n\n// ----- model ----- //\n\nnew Zdog.Rect({\n  addTo: illo,\n  width: 4,\n  height: 4,\n  translate: { x: -4, y: -4, z: 4 },\n  stroke: 1,\n  color: orange,\n});\n\nnew Zdog.RoundedRect({\n  addTo: illo,\n  width: 4,\n  height: 4,\n  cornerRadius: 1,\n  translate: { x: -4, y: 4, z: -4 },\n  stroke: 1,\n  color: eggplant,\n});\n\nnew Zdog.Ellipse({\n  addTo: illo,\n  diameter: 4,\n  translate: { x: 4, y: 4, z: 4 },\n  stroke: 1,\n  color: garnet,\n});\n\nnew Zdog.Polygon({\n  addTo: illo,\n  sides: 3,\n  radius: 2.5,\n  translate: { x: 4, y: -4, z: -4 },\n  stroke: 1,\n  color: orange,\n});\n\nnew Zdog.Shape({\n  addTo: illo,\n  path: [\n    { x: -1 },\n    { x:  1 },\n    { move: { y: -1 } },\n    { y: 1 },\n    { move: { z: -1 } },\n    { z: 1 },\n  ],\n  scale: 1.25,\n  stroke: 1,\n  color: offWhite,\n});\n\nnew Zdog.Hemisphere({\n  addTo: illo,\n  diameter: 5,\n  translate: { x: -4, y: -4, z: -4 },\n  color: garnet,\n  backface: gold,\n  stroke: false,\n});\n\nnew Zdog.Cylinder({\n  addTo: illo,\n  diameter: 5,\n  length: 4,\n  translate: { x: -4, y: 4, z: 4 },\n  color: gold,\n  backface: offWhite,\n  stroke: false,\n});\n\nnew Zdog.Cone({\n  addTo: illo,\n  diameter: 5,\n  length: 4,\n  translate: { x: 4, y: -4, z: 4 },\n  color: eggplant,\n  backface: garnet,\n  stroke: false,\n});\n\nnew Zdog.Box({\n  addTo: illo,\n  width: 5,\n  height: 5,\n  depth: 5,\n  translate: { x: 4, y: 4, z: -4 },\n  color: orange,\n  topFace: gold,\n  leftFace: garnet,\n  rightFace: garnet,\n  bottomFace: eggplant,\n  stroke: false,\n});\n\n// ----- animate ----- //\n\nvar ticker = 0;\nvar cycleCount = 360;\n\nfunction animate() {\n  if ( isSpinning ) {\n    var progress = ticker/cycleCount;\n    var theta = Zdog.easeInOut( progress % 1, 3 ) * TAU;\n    illo.rotate.y = theta * 2;\n    illo.rotate.x = Math.sin( theta ) * 0.5;\n    ticker++;\n  }\n  illo.updateRenderGraph();\n  requestAnimationFrame( animate );\n}\n\nanimate();\n\n"
  },
  {
    "path": "demos/solids/index.html",
    "content": "<!doctype html>\n<html lang=\"en\">\n<head>\n  <meta charset=\"utf-8\" />\n  <meta name=\"viewport\" content=\"width=device-width\" />\n\n  <title>solids</title>\n\n  <style>\n    html { height: 100%; }\n\n    body {\n      min-height: 100%;\n      margin: 0;\n    }\n\n    .illo {\n      display: block;\n      width: 100%;\n      height: 100%;\n      background: #FDB;\n      cursor: move;\n    }\n  </style>\n\n</head>\n<body>\n\n<canvas class=\"illo\"></canvas>\n\n<script src=\"../../js/boilerplate.js\"></script>\n<script src=\"../../js/canvas-renderer.js\"></script>\n<script src=\"../../js/svg-renderer.js\"></script>\n<script src=\"../../js/vector.js\"></script>\n<script src=\"../../js/anchor.js\"></script>\n<script src=\"../../js/path-command.js\"></script>\n<script src=\"../../js/shape.js\"></script>\n<script src=\"../../js/ellipse.js\"></script>\n<script src=\"../../js/rect.js\"></script>\n<script src=\"../../js/polygon.js\"></script>\n<script src=\"../../js/group.js\"></script>\n<script src=\"../../js/hemisphere.js\"></script>\n<script src=\"../../js/cylinder.js\"></script>\n<script src=\"../../js/cone.js\"></script>\n<script src=\"../../js/box.js\"></script>\n<script src=\"../../js/dragger.js\"></script>\n<script src=\"../../js/illustration.js\"></script>\n<script src=\"solids.js\"></script>\n\n</body>\n</html>\n"
  },
  {
    "path": "demos/solids/solids.js",
    "content": "// ----- setup ----- //\n\nvar illoElem = document.querySelector('.illo');\nvar sceneSize = 96;\nvar TAU = Zdog.TAU;\nvar ROOT3 = Math.sqrt( 3 );\nvar ROOT5 = Math.sqrt( 5 );\nvar PHI = ( 1 + ROOT5 )/2;\nvar isSpinning = true;\nvar viewRotation = new Zdog.Vector();\nvar displaySize;\n\n// colors\nvar eggplant = '#636';\nvar garnet = '#C25';\nvar orange = '#E62';\nvar gold = '#EA0';\nvar yellow = '#ED0';\n\nvar illo = new Zdog.Illustration({\n  element: illoElem,\n  scale: 8,\n  resize: 'fullscreen',\n  onResize: function( width, height ) {\n    displaySize = Math.min( width, height );\n    this.zoom = Math.floor( displaySize/sceneSize );\n  },\n});\n\nvar solids = [];\n\n// ----- hourglass ----- //\n\n( function() {\n\n  var hourglass = new Zdog.Anchor({\n    addTo: illo,\n    translate: { x: 0, y: -4 },\n  });\n\n  solids.push( hourglass );\n\n  var hemi = new Zdog.Hemisphere({\n    diameter: 2,\n    translate: { z: -1 },\n    addTo: hourglass,\n    color: garnet,\n    backface: orange,\n    stroke: false,\n  });\n\n  hemi.copy({\n    translate: { z: 1 },\n    rotate: { y: TAU/2 },\n    color: eggplant,\n    backface: gold,\n  });\n\n} )();\n\n// ----- sphere ----- //\n\n( function() {\n\n  var sphere = new Zdog.Anchor({\n    addTo: illo,\n    translate: { x: -4, y: -4 },\n  });\n\n  solids.push( sphere );\n\n  var hemi = new Zdog.Hemisphere({\n    diameter: 2,\n    addTo: sphere,\n    color: orange,\n    backface: eggplant,\n    stroke: false,\n  });\n\n  hemi.copy({\n    rotate: { y: TAU/2 },\n    color: eggplant,\n    backface: orange,\n  });\n\n} )();\n\n// ----- cylinder ----- //\n\nvar cylinder = new Zdog.Cylinder({\n  diameter: 2,\n  length: 2,\n  addTo: illo,\n  translate: { x: 4, y: -4 },\n  // rotate: { x: TAU/4 },\n  color: gold,\n  backface: garnet,\n  stroke: false,\n});\n\nsolids.push( cylinder );\n\n// ----- cone ----- //\n\nvar cone = new Zdog.Anchor({\n  addTo: illo,\n  translate: { x: -4, y: 0 },\n});\n\nsolids.push( cone );\n\nnew Zdog.Cone({\n  diameter: 2,\n  length: 2,\n  addTo: cone,\n  translate: { z: 1 },\n  rotate: { y: TAU/2 },\n  color: garnet,\n  backface: gold,\n  stroke: false,\n});\n\n// ----- tetrahedron ----- //\n\n( function() {\n\n  var tetrahedron = new Zdog.Anchor({\n    addTo: illo,\n    translate: { x: 0, y: 0 },\n    scale: 2.5,\n  });\n\n  var radius = 0.5;\n  var inradius = Math.cos( TAU/6 ) * radius;\n  var height = radius + inradius;\n\n  solids.push( tetrahedron );\n\n  var triangle = new Zdog.Polygon({\n    sides: 3,\n    radius: radius,\n    addTo: tetrahedron,\n    translate: { y: height/2 },\n    fill: true,\n    stroke: false,\n    color: eggplant,\n    // backface: false,\n  });\n\n  for ( var i = 0; i < 3; i++ ) {\n    var rotor1 = new Zdog.Anchor({\n      addTo: tetrahedron,\n      rotate: { y: TAU/3 * -i },\n    });\n    var rotor2 = new Zdog.Anchor({\n      addTo: rotor1,\n      translate: { z: inradius, y: height/2 },\n      rotate: { x: Math.acos( 1/3 ) * -1 + TAU/4 },\n    });\n    triangle.copy({\n      addTo: rotor2,\n      translate: { y: -inradius },\n      color: [ gold, garnet, orange ][i],\n    });\n  }\n\n  triangle.rotate.set({ x: -TAU/4, z: -TAU/2 });\n\n} )();\n\n// ----- octahedron ----- //\n\n( function() {\n\n  var octahedron = new Zdog.Anchor({\n    addTo: illo,\n    translate: { x: -4, y: 4 },\n    scale: 1.75,\n  });\n\n  solids.push( octahedron );\n\n  var colorWheel = [ eggplant, garnet, orange, gold, yellow ];\n\n  // radius of triangle with side length = 1\n  var radius = ROOT3/2 * 2/3;\n  var height = radius * 3/2;\n  var tilt = Math.asin( 0.5/height );\n\n  [ -1, 1 ].forEach( function( ySide ) {\n    for ( var i = 0; i < 4; i++ ) {\n      var rotor = new Zdog.Anchor({\n        addTo: octahedron,\n        rotate: { y: TAU/4 * ( i + 1.5 ) * -1 },\n      });\n\n      var anchor = new Zdog.Anchor({\n        addTo: rotor,\n        translate: { z: 0.5 },\n        rotate: { x: tilt * ySide },\n        // scale: { y: -ySide },\n      });\n\n      new Zdog.Polygon({\n        sides: 3,\n        radius: radius,\n        addTo: anchor,\n        translate: { y: -radius/2 * ySide },\n        scale: { y: ySide },\n        stroke: false,\n        fill: true,\n        color: colorWheel[ i + 0.5 + 0.5 * ySide ],\n        backface: false,\n      });\n    }\n  } );\n\n} )();\n\n// ----- cube ----- //\n\nvar cube = new Zdog.Box({\n  addTo: illo,\n  width: 2,\n  height: 2,\n  depth: 2,\n  translate: { x: 4, y: 0 },\n  topFace: yellow,\n  frontFace: gold,\n  leftFace: orange,\n  rightFace: orange,\n  rearFace: garnet,\n  bottomFace: eggplant,\n  stroke: false,\n});\n\nsolids.push( cube );\n\n// ----- dodecahedron ----- //\n\n( function() {\n\n  var dodecahedron = new Zdog.Anchor({\n    addTo: illo,\n    translate: { x: 0, y: 4 },\n    scale: 0.75,\n  });\n\n  solids.push( dodecahedron );\n\n  // https://en.wikipedia.org/wiki/Regular_dodecahedron#Dimensions\n  var midradius = ( PHI * PHI )/2;\n\n  // top & bottom faces\n  var face = new Zdog.Polygon({\n    sides: 5,\n    radius: 1,\n    addTo: dodecahedron,\n    translate: { y: -midradius },\n    rotate: { x: TAU/4 },\n    fill: true,\n    stroke: false,\n    color: yellow,\n    // backface: false,\n  });\n\n  face.copy({\n    translate: { y: midradius },\n    rotate: { x: -TAU/4 },\n    color: eggplant,\n  });\n\n  [ -1, 1 ].forEach( function( ySide ) {\n\n    var colorWheel = {\n      '-1': [ eggplant, garnet, gold, orange, garnet ],\n      1: [ yellow, gold, garnet, orange, gold ],\n    }[ ySide ];\n\n    for ( var i = 0; i < 5; i++ ) {\n      var rotor1 = new Zdog.Anchor({\n        addTo: dodecahedron,\n        rotate: { y: TAU/5 * i },\n      });\n      var rotor2 = new Zdog.Anchor({\n        addTo: rotor1,\n        rotate: { x: TAU/4 * ySide - Math.atan( 2 ) },\n      });\n\n      face.copy({\n        addTo: rotor2,\n        translate: { z: midradius },\n        rotate: { z: TAU/2 },\n        color: colorWheel[i],\n      });\n    }\n  } );\n\n} )();\n\n// ----- isocahedron ----- //\n\n( function() {\n\n  var isocahedron = new Zdog.Anchor({\n    addTo: illo,\n    translate: { x: 4, y: 4 },\n    scale: 1.2,\n  });\n\n  solids.push( isocahedron );\n\n  // geometry\n  // radius of triangle with side length = 1\n  var faceRadius = ROOT3/2 * 2/3;\n  var faceHeight = faceRadius * 3/2;\n  var capApothem = 0.5 / Math.tan( TAU/10 );\n  var capRadius = 0.5 / Math.sin( TAU/10 );\n  var capTilt = Math.asin( capApothem/faceHeight );\n  var capSagitta = capRadius - capApothem;\n  var sideTilt = Math.asin( capSagitta/faceHeight );\n  var sideHeight = Math.sqrt( faceHeight * faceHeight - capSagitta * capSagitta );\n\n  // var colorWheel = [ eggplant, garnet, orange, gold, yellow ];\n\n  [ -1, 1 ].forEach( function( ySide ) {\n    var capColors = {\n      '-1': [ garnet, gold, yellow, gold, orange ],\n      1: [ gold, garnet, eggplant, garnet, orange ],\n    }[ ySide ];\n\n    var sideColors = {\n      '-1': [ garnet, gold, yellow, orange, garnet ],\n      1: [ gold, garnet, eggplant, orange, orange ],\n    }[ ySide ];\n\n    for ( var i = 0; i < 5; i++ ) {\n      var rotor = new Zdog.Anchor({\n        addTo: isocahedron,\n        rotate: { y: TAU/5 * -i },\n        translate: { y: sideHeight/2 * ySide },\n      });\n\n      var capRotateX = -capTilt;\n      var isYPos = ySide > 0;\n      capRotateX += isYPos ? TAU/2 : 0;\n\n      var capAnchor = new Zdog.Anchor({\n        addTo: rotor,\n        translate: { z: capApothem * ySide },\n        rotate: { x: capRotateX },\n      });\n\n      // cap face\n      var face = new Zdog.Polygon({\n        sides: 3,\n        radius: faceRadius,\n        addTo: capAnchor,\n        translate: { y: -faceRadius/2 },\n        stroke: false,\n        fill: true,\n        color: capColors[i],\n        // backface: false,\n      });\n\n      var sideRotateX = -sideTilt;\n      sideRotateX += isYPos ? 0 : TAU/2;\n      var sideAnchor = capAnchor.copy({\n        rotate: { x: sideRotateX },\n      });\n\n      face.copy({\n        addTo: sideAnchor,\n        translate: { y: -faceRadius/2 },\n        rotate: { y: TAU/2 },\n        color: sideColors[i],\n      });\n\n    }\n  } );\n\n} )();\n\n// ----- animate ----- //\n\nvar keyframes = [\n  { x:   0, y:   0 },\n  { x:   0, y: TAU },\n  { x: TAU, y: TAU },\n];\n\nvar ticker = 0;\nvar cycleCount = 180;\nvar turnLimit = keyframes.length - 1;\n\nfunction animate() {\n  update();\n  illo.renderGraph();\n  requestAnimationFrame( animate );\n}\n\nanimate();\n\nfunction update() {\n\n  if ( isSpinning ) {\n    var progress = ticker/cycleCount;\n    var tween = Zdog.easeInOut( progress % 1, 4 );\n    var turn = Math.floor( progress % turnLimit );\n    var keyA = keyframes[ turn ];\n    var keyB = keyframes[ turn + 1 ];\n    viewRotation.x = Zdog.lerp( keyA.x, keyB.x, tween );\n    viewRotation.y = Zdog.lerp( keyA.y, keyB.y, tween );\n    ticker++;\n  }\n\n  solids.forEach( function( solid ) {\n    solid.rotate.set( viewRotation );\n  } );\n\n  illo.updateGraph();\n}\n\n// ----- inputs ----- //\n\nvar dragStartRX, dragStartRY;\n\nnew Zdog.Dragger({\n  startElement: illoElem,\n  onDragStart: function() {\n    isSpinning = false;\n    dragStartRX = viewRotation.x;\n    dragStartRY = viewRotation.y;\n  },\n  onDragMove: function( pointer, moveX, moveY ) {\n    viewRotation.x = dragStartRX - ( moveY/displaySize * TAU );\n    viewRotation.y = dragStartRY - ( moveX/displaySize * TAU );\n  },\n});\n"
  },
  {
    "path": "demos/strutter/index.html",
    "content": "<!doctype html>\n<html lang=\"en\">\n<head>\n  <meta charset=\"utf-8\" />\n  <meta name=\"viewport\" content=\"width=device-width\" />\n\n  <title>strutter</title>\n\n  <style>\n    html { height: 100%; }\n\n    body {\n      min-height: 100%;\n      margin: 0;\n    }\n\n    .illo {\n      display: block;\n      width: 100%;\n      height: 100%;\n      background: #FDB;\n      cursor: move;\n    }\n\n  </style>\n\n</head>\n<body>\n\n<canvas class=\"illo\"></canvas>\n\n<script src=\"../../js/boilerplate.js\"></script>\n<script src=\"../../js/canvas-renderer.js\"></script>\n<script src=\"../../js/vector.js\"></script>\n<script src=\"../../js/anchor.js\"></script>\n<script src=\"../../js/path-command.js\"></script>\n<script src=\"../../js/shape.js\"></script>\n<script src=\"../../js/ellipse.js\"></script>\n<script src=\"../../js/rect.js\"></script>\n<script src=\"../../js/rounded-rect.js\"></script>\n<script src=\"../../js/group.js\"></script>\n<script src=\"../../js/hemisphere.js\"></script>\n<script src=\"../../js/dragger.js\"></script>\n<script src=\"../../js/illustration.js\"></script>\n<script src=\"strutter.js\"></script>\n\n</body>\n</html>\n"
  },
  {
    "path": "demos/strutter/strutter.js",
    "content": "// ----- setup ----- //\n\nvar sceneSize = 48;\nvar isSpinning = true;\nvar TAU = Zdog.TAU;\n// colors\nvar gold = '#EA0';\nvar orange = '#C25';\nvar eggplant = '#636';\nvar midnight = '#424';\n\nvar illo = new Zdog.Illustration({\n  element: '.illo',\n  rotate: { y: -TAU/8 },\n  translate: { y: 4 },\n  dragRotate: true,\n  resize: 'fullscreen',\n  onDragStart: function() {\n    isSpinning = false;\n  },\n  onResize: function( width, height ) {\n    this.zoom = Math.floor( Math.min( width, height ) / sceneSize );\n  },\n});\n\n// ----- model ----- //\n\nvar hipX = 3;\n\nnew Zdog.Shape({\n  addTo: illo,\n  path: [ { x: -1 }, { x: 1 } ],\n  scale: hipX,\n  color: eggplant,\n  stroke: 4,\n});\n\nvar rightLeg = new Zdog.Shape({\n  addTo: illo,\n  path: [ { y: 0 }, { y: 12 } ],\n  translate: { x: -hipX },\n  rotate: { x: TAU/4 },\n  color: eggplant,\n  stroke: 4,\n});\n// foot\nnew Zdog.RoundedRect({\n  addTo: rightLeg,\n  width: 2,\n  height: 4,\n  cornerRadius: 1,\n  translate: { y: 14, z: 2 },\n  rotate: { x: TAU/4 },\n  color: orange,\n  fill: true,\n  stroke: 4,\n});\n\nvar plantAngle = -TAU/32 * 3;\nvar leftLeg = rightLeg.copyGraph({\n  translate: { x: hipX },\n  rotate: { x: plantAngle },\n  color: midnight,\n});\n\nleftLeg.children[0].rotate.set({ x: TAU/4 - plantAngle });\n\n// chest\nnew Zdog.Shape({\n  addTo: illo,\n  path: [ { x: -1 }, { x:  1 } ],\n  scale: 1.5,\n  translate: { y: -5.5, z: -3 },\n  color: orange,\n  stroke: 9,\n  fill: true,\n});\n\nvar armSize = 6;\n\n[ true, false ].forEach( function( isRight ) {\n  var xSide = isRight ? -1 : 1;\n\n  var upperArm = new Zdog.Shape({\n    addTo: illo,\n    path: [ { x: 0 }, { x: armSize } ],\n    scale: { x: xSide },\n    translate: { x: 4.5 * xSide, y: -8, z: -4 },\n    rotate: isRight ? { y: TAU/8, z: -TAU/16 } : { y: TAU/8 },\n    color: eggplant,\n    stroke: 4,\n  });\n\n  var forearm = new Zdog.Shape({\n    addTo: upperArm,\n    path: [ { x: 0 }, { x: armSize - 2 } ],\n    translate: { x: armSize },\n    rotate: isRight ? { z: TAU/16 * 3, y: TAU/4 } :\n      { z: -TAU/4, x: -TAU/32 * 2, y: TAU/8 },\n    color: orange,\n    stroke: 4,\n  });\n  // hand\n  new Zdog.Shape({\n    addTo: forearm,\n    translate: { x: armSize, z: 1 },\n    stroke: 6,\n    color: gold,\n  });\n\n} );\n\nvar head = new Zdog.Anchor({\n  addTo: illo,\n  translate: { y: -12, z: -10 },\n  rotate: { x: TAU/8 },\n});\n\n// face\nnew Zdog.Hemisphere({\n  addTo: head,\n  diameter: 12,\n  color: gold,\n  backface: orange,\n  rotate: { x: -TAU/4 },\n  stroke: false,\n});\n\nvar eye = new Zdog.Ellipse({\n  addTo: head,\n  diameter: 2,\n  quarters: 2,\n  translate: { x: -2, y: 1.5, z: 5 },\n  rotate: { z: -TAU/4 },\n  color: eggplant,\n  stroke: 0.5,\n  backface: false,\n});\neye.copy({\n  translate: { x: 2, y: 1.5, z: 5 },\n  rotate: { z: -TAU/4 },\n});\n// smile\nnew Zdog.Ellipse({\n  addTo: head,\n  diameter: 3,\n  quarters: 2,\n  translate: { y: 3, z: 4.5 },\n  rotate: { z: TAU/4 },\n  closed: true,\n  color: '#FED',\n  stroke: 0.5,\n  fill: true,\n  backface: false,\n});\n\nnew Zdog.Hemisphere({\n  addTo: head,\n  diameter: 12,\n  color: orange,\n  backface: gold,\n  rotate: { x: TAU/4 },\n  stroke: false,\n});\n\nvar brim = new Zdog.Anchor({\n  addTo: head,\n  scale: 5.5,\n  translate: { y: -0.5, z: 6 },\n});\n\nnew Zdog.Shape({\n  addTo: brim,\n  path: [\n    { x:  0, z: 0 },\n    { arc: [\n      { x: -1, z:  0 },\n      { x: -1, z: -1 },\n    ] },\n    { x:  -1, z: 0 },\n  ],\n  color: eggplant,\n  fill: true,\n});\n\nnew Zdog.Shape({\n  addTo: brim,\n  path: [\n    { x: -1, z:  0 },\n    { arc: [\n      { x: -1, z: 1 },\n      { x:  0, z: 1 },\n    ] },\n    { x: 0, z:  0 },\n  ],\n  color: eggplant,\n  fill: true,\n});\n\nbrim.copyGraph({\n  scale: brim.scale.copy().multiply({ x: -1 }),\n});\n\n// ----- animate ----- //\n\nvar ticker = 0;\nvar cycleCount = 150;\n\nfunction animate() {\n  if ( isSpinning ) {\n    var progress = ticker/cycleCount;\n    illo.rotate.y = Zdog.easeInOut( progress % 1, 4 ) * TAU - TAU/8;\n    ticker++;\n  }\n  illo.updateRenderGraph();\n  requestAnimationFrame( animate );\n}\n\nanimate();\n"
  },
  {
    "path": "demos/zdog-logo/index.html",
    "content": "<!doctype html>\n<html lang=\"en\">\n<head>\n  <meta charset=\"utf-8\" />\n  <meta name=\"viewport\" content=\"width=device-width\" />\n\n  <title>zdog logo</title>\n\n  <style>\n    html { height: 100%; }\n\n    body {\n      min-height: 100%;\n      margin: 0;\n    }\n\n    .illo {\n      display: block;\n      width: 100%;\n      height: 100%;\n      cursor: move;\n      background: #FDB;\n    }\n  </style>\n\n</head>\n<body>\n\n<canvas class=\"illo\"></canvas>\n\n<script src=\"../../js/boilerplate.js\"></script>\n<script src=\"../../js/canvas-renderer.js\"></script>\n<script src=\"../../js/vector.js\"></script>\n<script src=\"../../js/anchor.js\"></script>\n<script src=\"../../js/path-command.js\"></script>\n<script src=\"../../js/shape.js\"></script>\n<script src=\"../../js/ellipse.js\"></script>\n<script src=\"../../js/rect.js\"></script>\n<script src=\"../../js/group.js\"></script>\n<script src=\"../../js/dragger.js\"></script>\n<script src=\"../../js/illustration.js\"></script>\n<script src=\"zdog-logo.js\"></script>\n\n</body>\n</html>\n"
  },
  {
    "path": "demos/zdog-logo/zdog-logo.js",
    "content": "// ----- setup ----- //\n\nvar sceneSize = 100;\nvar isSpinning = true;\nvar TAU = Zdog.TAU;\nvar initRotate = { x: 20/360 * TAU, y: -50/360 * TAU };\nvar orange = '#E62';\nvar gold = '#EA0';\nvar eggplant = '#636';\nvar depth = 20;\nvar lineWidth = 8;\n\nvar illo = new Zdog.Illustration({\n  element: '.illo',\n  rotate: initRotate,\n  dragRotate: true,\n  resize: 'fullscreen',\n  onDragStart: function() {\n    isSpinning = false;\n  },\n  onResize: function( width, height ) {\n    this.zoom = Math.floor( Math.min( width, height ) * 2/sceneSize ) / 2;\n  },\n});\n\n// ----- model ----- //\n\nvar bigGroup = new Zdog.Group({\n  addTo: illo,\n});\n\nvar backGroup = new Zdog.Group({\n  addTo: bigGroup,\n  updateSort: true,\n});\n\n// top\nvar topSide = new Zdog.Rect({\n  addTo: backGroup,\n  width: 40,\n  height: depth,\n  translate: { y: -20 },\n  rotate: { x: TAU/4 },\n  fill: true,\n  stroke: lineWidth,\n  color: orange,\n});\ntopSide.copy({\n  translate: { y: 20 },\n  rotate: { x: -TAU/4 },\n});\n\nvar endCap = new Zdog.Rect({\n  addTo: backGroup,\n  width: depth,\n  height: 8,\n  translate: { x: -20, y: -16 },\n  rotate: { y: TAU/4 },\n  fill: true,\n  color: orange,\n  stroke: lineWidth,\n  backface: false,\n});\nendCap.copy({\n  translate: { x: 20, y: 16 },\n  rotate: { y: -TAU/4 },\n});\n\nvar cornerCap = endCap.copy({\n  height: 10,\n  translate: { x: -20, y: 15 },\n});\ncornerCap.copy({\n  translate: { x: 20, y: -15 },\n  rotate: { y: -TAU/4 },\n});\n\nvar underside = new Zdog.Rect({\n  addTo: backGroup,\n  width: 30,\n  height: depth,\n  translate: { x: -5, y: -12 },\n  rotate: { x: -TAU/4 },\n  stroke: lineWidth,\n  fill: true,\n  color: orange,\n});\nunderside.copy({\n  translate: { x: 5, y: 12 },\n  rotate: { x: TAU/4 },\n});\n\nvar slopeW = 30;\nvar slopeH = 22;\nvar slopeAngle = Math.atan( slopeH/slopeW );\n\nvar slope = new Zdog.Rect({\n  addTo: backGroup,\n  width: Math.sqrt( slopeH * slopeH + slopeW * slopeW ),\n  height: depth,\n  translate: { x: -5, y: -1 },\n  rotate: { x: TAU/4, y: slopeAngle },\n  stroke: lineWidth,\n  fill: true,\n  color: orange,\n  backface: false,\n});\n\nslope.copy({\n  translate: { x: 5, y: 1 },\n  rotate: { x: -TAU/4, y: -slopeAngle },\n});\n\n// tail\nnew Zdog.Ellipse({\n  addTo: backGroup,\n  diameter: 32,\n  quarters: 1,\n  closed: false,\n  translate: { x: 22, y: -4 },\n  rotate: { z: TAU/4 },\n  color: orange,\n  stroke: lineWidth,\n});\n\n// tongue\n\nvar tongueAnchor = new Zdog.Anchor({\n  addTo: backGroup,\n  translate: { x: -6, y: -7 },\n  rotate: { y: TAU/4 },\n\n});\n\nvar tongueH = 12;\nvar tongueS = 5;\nvar tongueTip = tongueH + tongueS;\n\nnew Zdog.Shape({\n  addTo: tongueAnchor,\n  path: [\n    { x: -tongueS, y: 0 },\n    { x:  tongueS, y: 0 },\n    { x:  tongueS, y: tongueH },\n    { arc: [\n      { x: tongueS, y: tongueTip },\n      { x: 0, y: tongueTip },\n    ] },\n    { arc: [\n      { x: -tongueS, y: tongueTip },\n      { x: -tongueS, y: tongueH },\n    ] },\n  ],\n  rotate: { x: TAU/4 - Math.atan( 16/22 ) },\n  fill: true,\n  stroke: 4,\n  color: eggplant,\n\n});\n\nvar foreGroup = new Zdog.Group({\n  addTo: bigGroup,\n  updateSort: true,\n});\n\nvar zFace = new Zdog.Shape({\n  addTo: foreGroup,\n  path: [\n    { x: -20, y: -20 },\n    { x:  20, y: -20 },\n    { x:  20, y: -10 },\n    { x: -10, y:  12 },\n    { x:  20, y:  12 },\n    { x:  20, y:  20 },\n    { x: -20, y:  20 },\n    { x: -20, y:  10 },\n    { x:  10, y: -12 },\n    { x: -20, y: -12 },\n  ],\n  translate: { z: depth/2 },\n  fill: true,\n  color: gold,\n  stroke: lineWidth,\n  backface: false,\n});\n\nzFace.copy({\n  scale: { x: -1 },\n  translate: { z: -depth/2 },\n  rotate: { y: TAU/2 },\n});\n\n// nose\nvar semiCircle = new Zdog.Ellipse({\n  addTo: backGroup,\n  quarters: 2,\n  scale: 8,\n  translate: { x: -26, y: -20 },\n  rotate: { y: TAU/4, z: TAU/4 },\n  fill: true,\n  stroke: 5,\n  color: eggplant,\n  closed: true,\n  // backface: false,\n});\n\n// ears\n// group & extra shape are hacks\nvar earGroup = new Zdog.Group({\n  addTo: illo,\n});\n\nvar ear = semiCircle.copy({\n  addTo: earGroup,\n  quarters: 2,\n  scale: 24,\n  rotate: { z: -TAU/16, x: TAU/16 },\n  translate: { x: 10, y: -14, z: depth },\n});\n\nnew Zdog.Shape({\n  visible: false,\n  addTo: ear,\n  translate: { z: 0.5, x: -0.5 },\n});\n\nearGroup.copyGraph({\n  scale: { z: -1 },\n});\n\n// ----- animate ----- //\n\nvar keyframes = [\n  { y:   0 + initRotate.y, z:   0 },\n  { y: TAU + initRotate.y, z:   0 },\n  { y: TAU + initRotate.y, z: TAU },\n];\n\nvar ticker = 0;\nvar cycleCount = 180;\nvar turnLimit = keyframes.length - 1;\n\nfunction animate() {\n  spin();\n  illo.updateRenderGraph();\n  requestAnimationFrame( animate );\n}\n\nfunction spin() {\n  if ( !isSpinning ) {\n    return;\n  }\n  var progress = ticker/cycleCount;\n  var tween = Zdog.easeInOut( progress % 1, 4 );\n  var turn = Math.floor( progress % turnLimit );\n  var keyA = keyframes[ turn ];\n  var keyB = keyframes[ turn + 1 ];\n  illo.rotate.y = Zdog.lerp( keyA.y, keyB.y, tween );\n  illo.rotate.z = Zdog.lerp( keyA.z, keyB.z, tween );\n  ticker++;\n}\n\nanimate();\n\n"
  },
  {
    "path": "dist/zdog.dist.js",
    "content": "/*!\n * Zdog v1.1.3\n * Round, flat, designer-friendly pseudo-3D engine\n * Licensed MIT\n * https://zzz.dog\n * Copyright 2020 Metafizzy\n */\n\n/**\n * Boilerplate & utils\n */\n\n( function( root, factory ) {\n  // module definition\n  if ( typeof module == 'object' && module.exports ) {\n    // CommonJS\n    module.exports = factory();\n  } else {\n    // browser global\n    root.Zdog = factory();\n  }\n}( this, function factory() {\n\nvar Zdog = {};\n\nZdog.TAU = Math.PI * 2;\n\nZdog.extend = function( a, b ) {\n  for ( var prop in b ) {\n    a[ prop ] = b[ prop ];\n  }\n  return a;\n};\n\nZdog.lerp = function( a, b, alpha ) {\n  return ( b - a ) * alpha + a;\n};\n\nZdog.modulo = function( num, div ) {\n  return ( ( num % div ) + div ) % div;\n};\n\nvar powerMultipliers = {\n  2: function( a ) {\n    return a * a;\n  },\n  3: function( a ) {\n    return a * a * a;\n  },\n  4: function( a ) {\n    return a * a * a * a;\n  },\n  5: function( a ) {\n    return a * a * a * a * a;\n  },\n};\n\nZdog.easeInOut = function( alpha, power ) {\n  if ( power == 1 ) {\n    return alpha;\n  }\n  alpha = Math.max( 0, Math.min( 1, alpha ) );\n  var isFirstHalf = alpha < 0.5;\n  var slope = isFirstHalf ? alpha : 1 - alpha;\n  slope /= 0.5;\n  // make easing steeper with more multiples\n  var powerMultiplier = powerMultipliers[ power ] || powerMultipliers[2];\n  var curve = powerMultiplier( slope );\n  curve /= 2;\n  return isFirstHalf ? curve : 1 - curve;\n};\n\nreturn Zdog;\n\n} ) );\n/**\n * CanvasRenderer\n */\n\n( function( root, factory ) {\n  // module definition\n  if ( typeof module == 'object' && module.exports ) {\n    // CommonJS\n    module.exports = factory();\n  } else {\n    // browser global\n    root.Zdog.CanvasRenderer = factory();\n  }\n}( this, function factory() {\n\nvar CanvasRenderer = { isCanvas: true };\n\nCanvasRenderer.begin = function( ctx ) {\n  ctx.beginPath();\n};\n\nCanvasRenderer.move = function( ctx, elem, point ) {\n  ctx.moveTo( point.x, point.y );\n};\n\nCanvasRenderer.line = function( ctx, elem, point ) {\n  ctx.lineTo( point.x, point.y );\n};\n\nCanvasRenderer.bezier = function( ctx, elem, cp0, cp1, end ) {\n  ctx.bezierCurveTo( cp0.x, cp0.y, cp1.x, cp1.y, end.x, end.y );\n};\n\nCanvasRenderer.closePath = function( ctx ) {\n  ctx.closePath();\n};\n\nCanvasRenderer.setPath = function() {};\n\nCanvasRenderer.renderPath = function( ctx, elem, pathCommands, isClosed ) {\n  this.begin( ctx, elem );\n  pathCommands.forEach( function( command ) {\n    command.render( ctx, elem, CanvasRenderer );\n  } );\n  if ( isClosed ) {\n    this.closePath( ctx, elem );\n  }\n};\n\nCanvasRenderer.stroke = function( ctx, elem, isStroke, color, lineWidth ) {\n  if ( !isStroke ) {\n    return;\n  }\n  ctx.strokeStyle = color;\n  ctx.lineWidth = lineWidth;\n  ctx.stroke();\n};\n\nCanvasRenderer.fill = function( ctx, elem, isFill, color ) {\n  if ( !isFill ) {\n    return;\n  }\n  ctx.fillStyle = color;\n  ctx.fill();\n};\n\nCanvasRenderer.end = function() {};\n\nreturn CanvasRenderer;\n\n} ) );\n/**\n * SvgRenderer\n */\n\n( function( root, factory ) {\n  // module definition\n  if ( typeof module == 'object' && module.exports ) {\n    // CommonJS\n    module.exports = factory();\n  } else {\n    // browser global\n    root.Zdog.SvgRenderer = factory();\n  }\n}( this, function factory() {\n\nvar SvgRenderer = { isSvg: true };\n\n// round path coordinates to 3 decimals\nvar round = SvgRenderer.round = function( num ) {\n  return Math.round( num * 1000 ) / 1000;\n};\n\nfunction getPointString( point ) {\n  return round( point.x ) + ',' + round( point.y ) + ' ';\n}\n\nSvgRenderer.begin = function() {};\n\nSvgRenderer.move = function( svg, elem, point ) {\n  return 'M' + getPointString( point );\n};\n\nSvgRenderer.line = function( svg, elem, point ) {\n  return 'L' + getPointString( point );\n};\n\nSvgRenderer.bezier = function( svg, elem, cp0, cp1, end ) {\n  return 'C' + getPointString( cp0 ) + getPointString( cp1 ) +\n    getPointString( end );\n};\n\nSvgRenderer.closePath = function( /* elem */) {\n  return 'Z';\n};\n\nSvgRenderer.setPath = function( svg, elem, pathValue ) {\n  elem.setAttribute( 'd', pathValue );\n};\n\nSvgRenderer.renderPath = function( svg, elem, pathCommands, isClosed ) {\n  var pathValue = '';\n  pathCommands.forEach( function( command ) {\n    pathValue += command.render( svg, elem, SvgRenderer );\n  } );\n  if ( isClosed ) {\n    pathValue += this.closePath( svg, elem );\n  }\n  this.setPath( svg, elem, pathValue );\n};\n\nSvgRenderer.stroke = function( svg, elem, isStroke, color, lineWidth ) {\n  if ( !isStroke ) {\n    return;\n  }\n  elem.setAttribute( 'stroke', color );\n  elem.setAttribute( 'stroke-width', lineWidth );\n};\n\nSvgRenderer.fill = function( svg, elem, isFill, color ) {\n  var fillColor = isFill ? color : 'none';\n  elem.setAttribute( 'fill', fillColor );\n};\n\nSvgRenderer.end = function( svg, elem ) {\n  svg.appendChild( elem );\n};\n\nreturn SvgRenderer;\n\n} ) );\n/**\n * Vector\n */\n\n( function( root, factory ) {\n  // module definition\n  if ( typeof module == 'object' && module.exports ) {\n    // CommonJS\n    module.exports = factory( require('./boilerplate') );\n  } else {\n    // browser global\n    var Zdog = root.Zdog;\n    Zdog.Vector = factory( Zdog );\n  }\n\n}( this, function factory( utils ) {\n\nfunction Vector( position ) {\n  this.set( position );\n}\n\nvar TAU = utils.TAU;\n\n// 'pos' = 'position'\nVector.prototype.set = function( pos ) {\n  this.x = pos && pos.x || 0;\n  this.y = pos && pos.y || 0;\n  this.z = pos && pos.z || 0;\n  return this;\n};\n\n// set coordinates without sanitizing\n// vec.write({ y: 2 }) only sets y coord\nVector.prototype.write = function( pos ) {\n  if ( !pos ) {\n    return this;\n  }\n  this.x = pos.x != undefined ? pos.x : this.x;\n  this.y = pos.y != undefined ? pos.y : this.y;\n  this.z = pos.z != undefined ? pos.z : this.z;\n  return this;\n};\n\nVector.prototype.rotate = function( rotation ) {\n  if ( !rotation ) {\n    return;\n  }\n  this.rotateZ( rotation.z );\n  this.rotateY( rotation.y );\n  this.rotateX( rotation.x );\n  return this;\n};\n\nVector.prototype.rotateZ = function( angle ) {\n  rotateProperty( this, angle, 'x', 'y' );\n};\n\nVector.prototype.rotateX = function( angle ) {\n  rotateProperty( this, angle, 'y', 'z' );\n};\n\nVector.prototype.rotateY = function( angle ) {\n  rotateProperty( this, angle, 'x', 'z' );\n};\n\nfunction rotateProperty( vec, angle, propA, propB ) {\n  if ( !angle || angle % TAU === 0 ) {\n    return;\n  }\n  var cos = Math.cos( angle );\n  var sin = Math.sin( angle );\n  var a = vec[ propA ];\n  var b = vec[ propB ];\n  vec[ propA ] = a * cos - b * sin;\n  vec[ propB ] = b * cos + a * sin;\n}\n\nVector.prototype.isSame = function( pos ) {\n  if ( !pos ) {\n    return false;\n  }\n  return this.x === pos.x && this.y === pos.y && this.z === pos.z;\n};\n\nVector.prototype.add = function( pos ) {\n  if ( !pos ) {\n    return this;\n  }\n  this.x += pos.x || 0;\n  this.y += pos.y || 0;\n  this.z += pos.z || 0;\n  return this;\n};\n\nVector.prototype.subtract = function( pos ) {\n  if ( !pos ) {\n    return this;\n  }\n  this.x -= pos.x || 0;\n  this.y -= pos.y || 0;\n  this.z -= pos.z || 0;\n  return this;\n};\n\nVector.prototype.multiply = function( pos ) {\n  if ( pos == undefined ) {\n    return this;\n  }\n  // multiple all values by same number\n  if ( typeof pos == 'number' ) {\n    this.x *= pos;\n    this.y *= pos;\n    this.z *= pos;\n  } else {\n    // multiply object\n    this.x *= pos.x != undefined ? pos.x : 1;\n    this.y *= pos.y != undefined ? pos.y : 1;\n    this.z *= pos.z != undefined ? pos.z : 1;\n  }\n  return this;\n};\n\nVector.prototype.transform = function( translation, rotation, scale ) {\n  this.multiply( scale );\n  this.rotate( rotation );\n  this.add( translation );\n  return this;\n};\n\nVector.prototype.lerp = function( pos, alpha ) {\n  this.x = utils.lerp( this.x, pos.x || 0, alpha );\n  this.y = utils.lerp( this.y, pos.y || 0, alpha );\n  this.z = utils.lerp( this.z, pos.z || 0, alpha );\n  return this;\n};\n\nVector.prototype.magnitude = function() {\n  var sum = this.x * this.x + this.y * this.y + this.z * this.z;\n  return getMagnitudeSqrt( sum );\n};\n\nfunction getMagnitudeSqrt( sum ) {\n  // PERF: check if sum ~= 1 and skip sqrt\n  if ( Math.abs( sum - 1 ) < 0.00000001 ) {\n    return 1;\n  }\n  return Math.sqrt( sum );\n}\n\nVector.prototype.magnitude2d = function() {\n  var sum = this.x * this.x + this.y * this.y;\n  return getMagnitudeSqrt( sum );\n};\n\nVector.prototype.copy = function() {\n  return new Vector( this );\n};\n\nreturn Vector;\n\n} ) );\n/**\n * Anchor\n */\n\n( function( root, factory ) {\n  // module definition\n  if ( typeof module == 'object' && module.exports ) {\n    // CommonJS\n    module.exports = factory( require('./boilerplate'), require('./vector'),\n        require('./canvas-renderer'), require('./svg-renderer') );\n  } else {\n    // browser global\n    var Zdog = root.Zdog;\n    Zdog.Anchor = factory( Zdog, Zdog.Vector, Zdog.CanvasRenderer,\n        Zdog.SvgRenderer );\n  }\n}( this, function factory( utils, Vector, CanvasRenderer, SvgRenderer ) {\n\nvar TAU = utils.TAU;\nvar onePoint = { x: 1, y: 1, z: 1 };\n\nfunction Anchor( options ) {\n  this.create( options || {} );\n}\n\nAnchor.prototype.create = function( options ) {\n  this.children = [];\n  // set defaults & options\n  utils.extend( this, this.constructor.defaults );\n  this.setOptions( options );\n\n  // transform\n  this.translate = new Vector( options.translate );\n  this.rotate = new Vector( options.rotate );\n  this.scale = new Vector( onePoint ).multiply( this.scale );\n  // origin\n  this.origin = new Vector();\n  this.renderOrigin = new Vector();\n\n  if ( this.addTo ) {\n    this.addTo.addChild( this );\n  }\n};\n\nAnchor.defaults = {};\n\nAnchor.optionKeys = Object.keys( Anchor.defaults ).concat([\n  'rotate',\n  'translate',\n  'scale',\n  'addTo',\n]);\n\nAnchor.prototype.setOptions = function( options ) {\n  var optionKeys = this.constructor.optionKeys;\n\n  for ( var key in options ) {\n    if ( optionKeys.indexOf( key ) != -1 ) {\n      this[ key ] = options[ key ];\n    }\n  }\n};\n\nAnchor.prototype.addChild = function( shape ) {\n  if ( this.children.indexOf( shape ) != -1 ) {\n    return;\n  }\n  shape.remove(); // remove previous parent\n  shape.addTo = this; // keep parent reference\n  this.children.push( shape );\n};\n\nAnchor.prototype.removeChild = function( shape ) {\n  var index = this.children.indexOf( shape );\n  if ( index != -1 ) {\n    this.children.splice( index, 1 );\n  }\n};\n\nAnchor.prototype.remove = function() {\n  if ( this.addTo ) {\n    this.addTo.removeChild( this );\n  }\n};\n\n// ----- update ----- //\n\nAnchor.prototype.update = function() {\n  // update self\n  this.reset();\n  // update children\n  this.children.forEach( function( child ) {\n    child.update();\n  } );\n  this.transform( this.translate, this.rotate, this.scale );\n};\n\nAnchor.prototype.reset = function() {\n  this.renderOrigin.set( this.origin );\n};\n\nAnchor.prototype.transform = function( translation, rotation, scale ) {\n  this.renderOrigin.transform( translation, rotation, scale );\n  // transform children\n  this.children.forEach( function( child ) {\n    child.transform( translation, rotation, scale );\n  } );\n};\n\nAnchor.prototype.updateGraph = function() {\n  this.update();\n  this.updateFlatGraph();\n  this.flatGraph.forEach( function( item ) {\n    item.updateSortValue();\n  } );\n  // z-sort\n  this.flatGraph.sort( Anchor.shapeSorter );\n};\n\nAnchor.shapeSorter = function( a, b ) {\n  return a.sortValue - b.sortValue;\n};\n\n// custom getter to check for flatGraph before using it\nObject.defineProperty( Anchor.prototype, 'flatGraph', {\n  get: function() {\n    if ( !this._flatGraph ) {\n      this.updateFlatGraph();\n    }\n    return this._flatGraph;\n  },\n  set: function( graph ) {\n    this._flatGraph = graph;\n  },\n} );\n\nAnchor.prototype.updateFlatGraph = function() {\n  this.flatGraph = this.getFlatGraph();\n};\n\n// return Array of self & all child graph items\nAnchor.prototype.getFlatGraph = function() {\n  var flatGraph = [ this ];\n  return this.addChildFlatGraph( flatGraph );\n};\n\nAnchor.prototype.addChildFlatGraph = function( flatGraph ) {\n  this.children.forEach( function( child ) {\n    var childFlatGraph = child.getFlatGraph();\n    Array.prototype.push.apply( flatGraph, childFlatGraph );\n  } );\n  return flatGraph;\n};\n\nAnchor.prototype.updateSortValue = function() {\n  this.sortValue = this.renderOrigin.z;\n};\n\n// ----- render ----- //\n\nAnchor.prototype.render = function() {};\n\n// TODO refactor out CanvasRenderer so its not a dependency within anchor.js\nAnchor.prototype.renderGraphCanvas = function( ctx ) {\n  if ( !ctx ) {\n    throw new Error( 'ctx is ' + ctx + '. ' +\n      'Canvas context required for render. Check .renderGraphCanvas( ctx ).' );\n  }\n  this.flatGraph.forEach( function( item ) {\n    item.render( ctx, CanvasRenderer );\n  } );\n};\n\nAnchor.prototype.renderGraphSvg = function( svg ) {\n  if ( !svg ) {\n    throw new Error( 'svg is ' + svg + '. ' +\n      'SVG required for render. Check .renderGraphSvg( svg ).' );\n  }\n  this.flatGraph.forEach( function( item ) {\n    item.render( svg, SvgRenderer );\n  } );\n};\n\n// ----- misc ----- //\n\nAnchor.prototype.copy = function( options ) {\n  // copy options\n  var itemOptions = {};\n  var optionKeys = this.constructor.optionKeys;\n  optionKeys.forEach( function( key ) {\n    itemOptions[ key ] = this[ key ];\n  }, this );\n  // add set options\n  utils.extend( itemOptions, options );\n  var ItemClass = this.constructor;\n  return new ItemClass( itemOptions );\n};\n\nAnchor.prototype.copyGraph = function( options ) {\n  var clone = this.copy( options );\n  this.children.forEach( function( child ) {\n    child.copyGraph({\n      addTo: clone,\n    });\n  } );\n  return clone;\n};\n\nAnchor.prototype.normalizeRotate = function() {\n  this.rotate.x = utils.modulo( this.rotate.x, TAU );\n  this.rotate.y = utils.modulo( this.rotate.y, TAU );\n  this.rotate.z = utils.modulo( this.rotate.z, TAU );\n};\n\n// ----- subclass ----- //\n\nfunction getSubclass( Super ) {\n  return function( defaults ) {\n    // create constructor\n    function Item( options ) {\n      this.create( options || {} );\n    }\n\n    Item.prototype = Object.create( Super.prototype );\n    Item.prototype.constructor = Item;\n\n    Item.defaults = utils.extend( {}, Super.defaults );\n    utils.extend( Item.defaults, defaults );\n    // create optionKeys\n    Item.optionKeys = Super.optionKeys.slice( 0 );\n    // add defaults keys to optionKeys, dedupe\n    Object.keys( Item.defaults ).forEach( function( key ) {\n      if ( !Item.optionKeys.indexOf( key ) != 1 ) {\n        Item.optionKeys.push( key );\n      }\n    } );\n\n    Item.subclass = getSubclass( Item );\n\n    return Item;\n  };\n}\n\nAnchor.subclass = getSubclass( Anchor );\n\nreturn Anchor;\n\n} ) );\n/**\n * Dragger\n */\n\n( function( root, factory ) {\n  // module definition\n  if ( typeof module == 'object' && module.exports ) {\n    // CommonJS\n    module.exports = factory();\n  } else {\n    // browser global\n    root.Zdog.Dragger = factory();\n  }\n}( this, function factory() {\n\n// quick & dirty drag event stuff\n// messes up if multiple pointers/touches\n\n// check for browser window #85\nvar hasWindow = typeof window != 'undefined';\n// event support, default to mouse events\nvar downEvent = 'mousedown';\nvar moveEvent = 'mousemove';\nvar upEvent = 'mouseup';\nif ( hasWindow ) {\n  if ( window.PointerEvent ) {\n    // PointerEvent, Chrome\n    downEvent = 'pointerdown';\n    moveEvent = 'pointermove';\n    upEvent = 'pointerup';\n  } else if ( 'ontouchstart' in window ) {\n    // Touch Events, iOS Safari\n    downEvent = 'touchstart';\n    moveEvent = 'touchmove';\n    upEvent = 'touchend';\n  }\n}\n\nfunction noop() {}\n\nfunction Dragger( options ) {\n  this.create( options || {} );\n}\n\nDragger.prototype.create = function( options ) {\n  this.onDragStart = options.onDragStart || noop;\n  this.onDragMove = options.onDragMove || noop;\n  this.onDragEnd = options.onDragEnd || noop;\n\n  this.bindDrag( options.startElement );\n};\n\nDragger.prototype.bindDrag = function( element ) {\n  element = this.getQueryElement( element );\n  if ( !element ) {\n    return;\n  }\n  // disable browser gestures #53\n  element.style.touchAction = 'none';\n  element.addEventListener( downEvent, this );\n};\n\nDragger.prototype.getQueryElement = function( element ) {\n  if ( typeof element == 'string' ) {\n    // with string, query selector\n    element = document.querySelector( element );\n  }\n  return element;\n};\n\nDragger.prototype.handleEvent = function( event ) {\n  var method = this[ 'on' + event.type ];\n  if ( method ) {\n    method.call( this, event );\n  }\n};\n\nDragger.prototype.onmousedown =\nDragger.prototype.onpointerdown = function( event ) {\n  this.dragStart( event, event );\n};\n\nDragger.prototype.ontouchstart = function( event ) {\n  this.dragStart( event, event.changedTouches[0] );\n};\n\nDragger.prototype.dragStart = function( event, pointer ) {\n  event.preventDefault();\n  this.dragStartX = pointer.pageX;\n  this.dragStartY = pointer.pageY;\n  if ( hasWindow ) {\n    window.addEventListener( moveEvent, this );\n    window.addEventListener( upEvent, this );\n  }\n  this.onDragStart( pointer );\n};\n\nDragger.prototype.ontouchmove = function( event ) {\n  // HACK, moved touch may not be first\n  this.dragMove( event, event.changedTouches[0] );\n};\n\nDragger.prototype.onmousemove =\nDragger.prototype.onpointermove = function( event ) {\n  this.dragMove( event, event );\n};\n\nDragger.prototype.dragMove = function( event, pointer ) {\n  event.preventDefault();\n  var moveX = pointer.pageX - this.dragStartX;\n  var moveY = pointer.pageY - this.dragStartY;\n  this.onDragMove( pointer, moveX, moveY );\n};\n\nDragger.prototype.onmouseup =\nDragger.prototype.onpointerup =\nDragger.prototype.ontouchend =\nDragger.prototype.dragEnd = function( /* event */) {\n  window.removeEventListener( moveEvent, this );\n  window.removeEventListener( upEvent, this );\n  this.onDragEnd();\n};\n\nreturn Dragger;\n\n} ) );\n/**\n * Illustration\n */\n\n( function( root, factory ) {\n  // module definition\n  if ( typeof module == 'object' && module.exports ) {\n    // CommonJS\n    module.exports = factory( require('./boilerplate'), require('./anchor'),\n        require('./dragger') );\n  } else {\n    // browser global\n    var Zdog = root.Zdog;\n    Zdog.Illustration = factory( Zdog, Zdog.Anchor, Zdog.Dragger );\n  }\n}( this, function factory( utils, Anchor, Dragger ) {\n\nfunction noop() {}\nvar TAU = utils.TAU;\n\nvar Illustration = Anchor.subclass({\n  element: undefined,\n  centered: true,\n  zoom: 1,\n  dragRotate: false,\n  resize: false,\n  onPrerender: noop,\n  onDragStart: noop,\n  onDragMove: noop,\n  onDragEnd: noop,\n  onResize: noop,\n});\n\nutils.extend( Illustration.prototype, Dragger.prototype );\n\nIllustration.prototype.create = function( options ) {\n  Anchor.prototype.create.call( this, options );\n  Dragger.prototype.create.call( this, options );\n  this.setElement( this.element );\n  this.setDragRotate( this.dragRotate );\n  this.setResize( this.resize );\n};\n\nIllustration.prototype.setElement = function( element ) {\n  element = this.getQueryElement( element );\n  if ( !element ) {\n    throw new Error( 'Zdog.Illustration element required. Set to ' + element );\n  }\n\n  var nodeName = element.nodeName.toLowerCase();\n  if ( nodeName == 'canvas' ) {\n    this.setCanvas( element );\n  } else if ( nodeName == 'svg' ) {\n    this.setSvg( element );\n  }\n};\n\nIllustration.prototype.setSize = function( width, height ) {\n  width = Math.round( width );\n  height = Math.round( height );\n  if ( this.isCanvas ) {\n    this.setSizeCanvas( width, height );\n  } else if ( this.isSvg ) {\n    this.setSizeSvg( width, height );\n  }\n};\n\nIllustration.prototype.setResize = function( resize ) {\n  this.resize = resize;\n  // create resize event listener\n  if ( !this.resizeListener ) {\n    this.resizeListener = this.onWindowResize.bind( this );\n  }\n  // add/remove event listener\n  if ( resize ) {\n    window.addEventListener( 'resize', this.resizeListener );\n    this.onWindowResize();\n  } else {\n    window.removeEventListener( 'resize', this.resizeListener );\n  }\n};\n\n// TODO debounce this?\nIllustration.prototype.onWindowResize = function() {\n  this.setMeasuredSize();\n  this.onResize( this.width, this.height );\n};\n\nIllustration.prototype.setMeasuredSize = function() {\n  var width, height;\n  var isFullscreen = this.resize == 'fullscreen';\n  if ( isFullscreen ) {\n    width = window.innerWidth;\n    height = window.innerHeight;\n  } else {\n    var rect = this.element.getBoundingClientRect();\n    width = rect.width;\n    height = rect.height;\n  }\n  this.setSize( width, height );\n};\n\n// ----- render ----- //\n\nIllustration.prototype.renderGraph = function( item ) {\n  if ( this.isCanvas ) {\n    this.renderGraphCanvas( item );\n  } else if ( this.isSvg ) {\n    this.renderGraphSvg( item );\n  }\n};\n\n// combo method\nIllustration.prototype.updateRenderGraph = function( item ) {\n  this.updateGraph();\n  this.renderGraph( item );\n};\n\n// ----- canvas ----- //\n\nIllustration.prototype.setCanvas = function( element ) {\n  this.element = element;\n  this.isCanvas = true;\n  // update related properties\n  this.ctx = this.element.getContext('2d');\n  // set initial size\n  this.setSizeCanvas( element.width, element.height );\n};\n\nIllustration.prototype.setSizeCanvas = function( width, height ) {\n  this.width = width;\n  this.height = height;\n  // up-rez for hi-DPI devices\n  var pixelRatio = this.pixelRatio = window.devicePixelRatio || 1;\n  this.element.width = this.canvasWidth = width * pixelRatio;\n  this.element.height = this.canvasHeight = height * pixelRatio;\n  var needsHighPixelRatioSizing = pixelRatio > 1 && !this.resize;\n  if ( needsHighPixelRatioSizing ) {\n    this.element.style.width = width + 'px';\n    this.element.style.height = height + 'px';\n  }\n};\n\nIllustration.prototype.renderGraphCanvas = function( item ) {\n  item = item || this;\n  this.prerenderCanvas();\n  Anchor.prototype.renderGraphCanvas.call( item, this.ctx );\n  this.postrenderCanvas();\n};\n\nIllustration.prototype.prerenderCanvas = function() {\n  var ctx = this.ctx;\n  ctx.lineCap = 'round';\n  ctx.lineJoin = 'round';\n  ctx.clearRect( 0, 0, this.canvasWidth, this.canvasHeight );\n  ctx.save();\n  if ( this.centered ) {\n    var centerX = this.width / 2 * this.pixelRatio;\n    var centerY = this.height / 2 * this.pixelRatio;\n    ctx.translate( centerX, centerY );\n  }\n  var scale = this.pixelRatio * this.zoom;\n  ctx.scale( scale, scale );\n  this.onPrerender( ctx );\n};\n\nIllustration.prototype.postrenderCanvas = function() {\n  this.ctx.restore();\n};\n\n// ----- svg ----- //\n\nIllustration.prototype.setSvg = function( element ) {\n  this.element = element;\n  this.isSvg = true;\n  this.pixelRatio = 1;\n  // set initial size from width & height attributes\n  var width = element.getAttribute('width');\n  var height = element.getAttribute('height');\n  this.setSizeSvg( width, height );\n};\n\nIllustration.prototype.setSizeSvg = function( width, height ) {\n  this.width = width;\n  this.height = height;\n  var viewWidth = width / this.zoom;\n  var viewHeight = height / this.zoom;\n  var viewX = this.centered ? -viewWidth/2 : 0;\n  var viewY = this.centered ? -viewHeight/2 : 0;\n  this.element.setAttribute( 'viewBox', viewX + ' ' + viewY + ' ' +\n    viewWidth + ' ' + viewHeight );\n  if ( this.resize ) {\n    // remove size attributes, let size be determined by viewbox\n    this.element.removeAttribute('width');\n    this.element.removeAttribute('height');\n  } else {\n    this.element.setAttribute( 'width', width );\n    this.element.setAttribute( 'height', height );\n  }\n};\n\nIllustration.prototype.renderGraphSvg = function( item ) {\n  item = item || this;\n  empty( this.element );\n  this.onPrerender( this.element );\n  Anchor.prototype.renderGraphSvg.call( item, this.element );\n};\n\nfunction empty( element ) {\n  while ( element.firstChild ) {\n    element.removeChild( element.firstChild );\n  }\n}\n\n// ----- drag ----- //\n\nIllustration.prototype.setDragRotate = function( item ) {\n  if ( !item ) {\n    return;\n  } else if ( item === true ) {\n    /* eslint consistent-this: \"off\" */\n    item = this;\n  }\n  this.dragRotate = item;\n\n  this.bindDrag( this.element );\n};\n\nIllustration.prototype.dragStart = function( /* event, pointer */) {\n  this.dragStartRX = this.dragRotate.rotate.x;\n  this.dragStartRY = this.dragRotate.rotate.y;\n  Dragger.prototype.dragStart.apply( this, arguments );\n};\n\nIllustration.prototype.dragMove = function( event, pointer ) {\n  var moveX = pointer.pageX - this.dragStartX;\n  var moveY = pointer.pageY - this.dragStartY;\n  var displaySize = Math.min( this.width, this.height );\n  var moveRY = moveX/displaySize * TAU;\n  var moveRX = moveY/displaySize * TAU;\n  this.dragRotate.rotate.x = this.dragStartRX - moveRX;\n  this.dragRotate.rotate.y = this.dragStartRY - moveRY;\n  Dragger.prototype.dragMove.apply( this, arguments );\n};\n\nreturn Illustration;\n\n} ) );\n/**\n * PathCommand\n */\n\n( function( root, factory ) {\n  // module definition\n  if ( typeof module == 'object' && module.exports ) {\n    // CommonJS\n    module.exports = factory( require('./vector') );\n  } else {\n    // browser global\n    var Zdog = root.Zdog;\n    Zdog.PathCommand = factory( Zdog.Vector );\n  }\n}( this, function factory( Vector ) {\n\nfunction PathCommand( method, points, previousPoint ) {\n  this.method = method;\n  this.points = points.map( mapVectorPoint );\n  this.renderPoints = points.map( mapNewVector );\n  this.previousPoint = previousPoint;\n  this.endRenderPoint = this.renderPoints[ this.renderPoints.length - 1 ];\n  // arc actions come with previous point & corner point\n  // but require bezier control points\n  if ( method == 'arc' ) {\n    this.controlPoints = [ new Vector(), new Vector() ];\n  }\n}\n\nfunction mapVectorPoint( point ) {\n  if ( point instanceof Vector ) {\n    return point;\n  } else {\n    return new Vector( point );\n  }\n}\n\nfunction mapNewVector( point ) {\n  return new Vector( point );\n}\n\nPathCommand.prototype.reset = function() {\n  // reset renderPoints back to orignal points position\n  var points = this.points;\n  this.renderPoints.forEach( function( renderPoint, i ) {\n    var point = points[i];\n    renderPoint.set( point );\n  } );\n};\n\nPathCommand.prototype.transform = function( translation, rotation, scale ) {\n  this.renderPoints.forEach( function( renderPoint ) {\n    renderPoint.transform( translation, rotation, scale );\n  } );\n};\n\nPathCommand.prototype.render = function( ctx, elem, renderer ) {\n  return this[ this.method ]( ctx, elem, renderer );\n};\n\nPathCommand.prototype.move = function( ctx, elem, renderer ) {\n  return renderer.move( ctx, elem, this.renderPoints[0] );\n};\n\nPathCommand.prototype.line = function( ctx, elem, renderer ) {\n  return renderer.line( ctx, elem, this.renderPoints[0] );\n};\n\nPathCommand.prototype.bezier = function( ctx, elem, renderer ) {\n  var cp0 = this.renderPoints[0];\n  var cp1 = this.renderPoints[1];\n  var end = this.renderPoints[2];\n  return renderer.bezier( ctx, elem, cp0, cp1, end );\n};\n\nvar arcHandleLength = 9/16;\n\nPathCommand.prototype.arc = function( ctx, elem, renderer ) {\n  var prev = this.previousPoint;\n  var corner = this.renderPoints[0];\n  var end = this.renderPoints[1];\n  var cp0 = this.controlPoints[0];\n  var cp1 = this.controlPoints[1];\n  cp0.set( prev ).lerp( corner, arcHandleLength );\n  cp1.set( end ).lerp( corner, arcHandleLength );\n  return renderer.bezier( ctx, elem, cp0, cp1, end );\n};\n\nreturn PathCommand;\n\n} ) );\n/**\n * Shape\n */\n\n( function( root, factory ) {\n  // module definition\n  if ( typeof module == 'object' && module.exports ) {\n    // CommonJS\n    module.exports = factory( require('./boilerplate'), require('./vector'),\n        require('./path-command'), require('./anchor') );\n  } else {\n    // browser global\n    var Zdog = root.Zdog;\n    Zdog.Shape = factory( Zdog, Zdog.Vector, Zdog.PathCommand, Zdog.Anchor );\n  }\n}( this, function factory( utils, Vector, PathCommand, Anchor ) {\n\nvar Shape = Anchor.subclass({\n  stroke: 1,\n  fill: false,\n  color: '#333',\n  closed: true,\n  visible: true,\n  path: [ {} ],\n  front: { z: 1 },\n  backface: true,\n});\n\nShape.prototype.create = function( options ) {\n  Anchor.prototype.create.call( this, options );\n  this.updatePath();\n  // front\n  this.front = new Vector( options.front || this.front );\n  this.renderFront = new Vector( this.front );\n  this.renderNormal = new Vector();\n};\n\nvar actionNames = [\n  'move',\n  'line',\n  'bezier',\n  'arc',\n];\n\nShape.prototype.updatePath = function() {\n  this.setPath();\n  this.updatePathCommands();\n};\n\n// place holder for Ellipse, Rect, etc.\nShape.prototype.setPath = function() {};\n\n// parse path into PathCommands\nShape.prototype.updatePathCommands = function() {\n  var previousPoint;\n  this.pathCommands = this.path.map( function( pathPart, i ) {\n    // pathPart can be just vector coordinates -> { x, y, z }\n    // or path instruction -> { arc: [ {x0,y0,z0}, {x1,y1,z1} ] }\n    var keys = Object.keys( pathPart );\n    var method = keys[0];\n    var points = pathPart[ method ];\n    // default to line if no instruction\n    var isInstruction = keys.length == 1 && actionNames.indexOf( method ) != -1;\n    if ( !isInstruction ) {\n      method = 'line';\n      points = pathPart;\n    }\n    // munge single-point methods like line & move without arrays\n    var isLineOrMove = method == 'line' || method == 'move';\n    var isPointsArray = Array.isArray( points );\n    if ( isLineOrMove && !isPointsArray ) {\n      points = [ points ];\n    }\n\n    // first action is always move\n    method = i === 0 ? 'move' : method;\n    // arcs require previous last point\n    var command = new PathCommand( method, points, previousPoint );\n    // update previousLastPoint\n    previousPoint = command.endRenderPoint;\n    return command;\n  } );\n};\n\n// ----- update ----- //\n\nShape.prototype.reset = function() {\n  this.renderOrigin.set( this.origin );\n  this.renderFront.set( this.front );\n  // reset command render points\n  this.pathCommands.forEach( function( command ) {\n    command.reset();\n  } );\n};\n\nShape.prototype.transform = function( translation, rotation, scale ) {\n  // calculate render points backface visibility & cone/hemisphere shapes\n  this.renderOrigin.transform( translation, rotation, scale );\n  this.renderFront.transform( translation, rotation, scale );\n  this.renderNormal.set( this.renderOrigin ).subtract( this.renderFront );\n  // transform points\n  this.pathCommands.forEach( function( command ) {\n    command.transform( translation, rotation, scale );\n  } );\n  // transform children\n  this.children.forEach( function( child ) {\n    child.transform( translation, rotation, scale );\n  } );\n};\n\nShape.prototype.updateSortValue = function() {\n  // sort by average z of all points\n  // def not geometrically correct, but works for me\n  var pointCount = this.pathCommands.length;\n  var firstPoint = this.pathCommands[0].endRenderPoint;\n  var lastPoint = this.pathCommands[ pointCount - 1 ].endRenderPoint;\n  // ignore the final point if self closing shape\n  var isSelfClosing = pointCount > 2 && firstPoint.isSame( lastPoint );\n  if ( isSelfClosing ) {\n    pointCount -= 1;\n  }\n\n  var sortValueTotal = 0;\n  for ( var i = 0; i < pointCount; i++ ) {\n    sortValueTotal += this.pathCommands[i].endRenderPoint.z;\n  }\n  this.sortValue = sortValueTotal/pointCount;\n};\n\n// ----- render ----- //\n\nShape.prototype.render = function( ctx, renderer ) {\n  var length = this.pathCommands.length;\n  if ( !this.visible || !length ) {\n    return;\n  }\n  // do not render if hiding backface\n  this.isFacingBack = this.renderNormal.z > 0;\n  if ( !this.backface && this.isFacingBack ) {\n    return;\n  }\n  if ( !renderer ) {\n    throw new Error( 'Zdog renderer required. Set to ' + renderer );\n  }\n  // render dot or path\n  var isDot = length == 1;\n  if ( renderer.isCanvas && isDot ) {\n    this.renderCanvasDot( ctx, renderer );\n  } else {\n    this.renderPath( ctx, renderer );\n  }\n};\n\nvar TAU = utils.TAU;\n// Safari does not render lines with no size, have to render circle instead\nShape.prototype.renderCanvasDot = function( ctx ) {\n  var lineWidth = this.getLineWidth();\n  if ( !lineWidth ) {\n    return;\n  }\n  ctx.fillStyle = this.getRenderColor();\n  var point = this.pathCommands[0].endRenderPoint;\n  ctx.beginPath();\n  var radius = lineWidth/2;\n  ctx.arc( point.x, point.y, radius, 0, TAU );\n  ctx.fill();\n};\n\nShape.prototype.getLineWidth = function() {\n  if ( !this.stroke ) {\n    return 0;\n  }\n  if ( this.stroke == true ) {\n    return 1;\n  }\n  return this.stroke;\n};\n\nShape.prototype.getRenderColor = function() {\n  // use backface color if applicable\n  var isBackfaceColor = typeof this.backface == 'string' && this.isFacingBack;\n  var color = isBackfaceColor ? this.backface : this.color;\n  return color;\n};\n\nShape.prototype.renderPath = function( ctx, renderer ) {\n  var elem = this.getRenderElement( ctx, renderer );\n  var isTwoPoints = this.pathCommands.length == 2 &&\n    this.pathCommands[1].method == 'line';\n  var isClosed = !isTwoPoints && this.closed;\n  var color = this.getRenderColor();\n\n  renderer.renderPath( ctx, elem, this.pathCommands, isClosed );\n  renderer.stroke( ctx, elem, this.stroke, color, this.getLineWidth() );\n  renderer.fill( ctx, elem, this.fill, color );\n  renderer.end( ctx, elem );\n};\n\nvar svgURI = 'http://www.w3.org/2000/svg';\n\nShape.prototype.getRenderElement = function( ctx, renderer ) {\n  if ( !renderer.isSvg ) {\n    return;\n  }\n  if ( !this.svgElement ) {\n    // create svgElement\n    this.svgElement = document.createElementNS( svgURI, 'path' );\n    this.svgElement.setAttribute( 'stroke-linecap', 'round' );\n    this.svgElement.setAttribute( 'stroke-linejoin', 'round' );\n  }\n  return this.svgElement;\n};\n\nreturn Shape;\n\n} ) );\n/**\n * Group\n */\n\n( function( root, factory ) {\n  // module definition\n  if ( typeof module == 'object' && module.exports ) {\n    // CommonJS\n    module.exports = factory( require('./anchor') );\n  } else {\n    // browser global\n    var Zdog = root.Zdog;\n    Zdog.Group = factory( Zdog.Anchor );\n  }\n}( this, function factory( Anchor ) {\n\nvar Group = Anchor.subclass({\n  updateSort: false,\n  visible: true,\n});\n\n// ----- update ----- //\n\nGroup.prototype.updateSortValue = function() {\n  var sortValueTotal = 0;\n  this.flatGraph.forEach( function( item ) {\n    item.updateSortValue();\n    sortValueTotal += item.sortValue;\n  } );\n  // average sort value of all points\n  // def not geometrically correct, but works for me\n  this.sortValue = sortValueTotal / this.flatGraph.length;\n\n  if ( this.updateSort ) {\n    this.flatGraph.sort( Anchor.shapeSorter );\n  }\n};\n\n// ----- render ----- //\n\nGroup.prototype.render = function( ctx, renderer ) {\n  if ( !this.visible ) {\n    return;\n  }\n\n  this.flatGraph.forEach( function( item ) {\n    item.render( ctx, renderer );\n  } );\n};\n\n// actual group flatGraph only used inside group\nGroup.prototype.updateFlatGraph = function() {\n  // do not include self\n  var flatGraph = [];\n  this.flatGraph = this.addChildFlatGraph( flatGraph );\n};\n\n// do not include children, group handles rendering & sorting internally\nGroup.prototype.getFlatGraph = function() {\n  return [ this ];\n};\n\nreturn Group;\n\n} ) );\n/**\n * Rect\n */\n\n( function( root, factory ) {\n  // module definition\n  if ( typeof module == 'object' && module.exports ) {\n    // CommonJS\n    module.exports = factory( require('./shape') );\n  } else {\n    // browser global\n    var Zdog = root.Zdog;\n    Zdog.Rect = factory( Zdog.Shape );\n  }\n}( this, function factory( Shape ) {\n\nvar Rect = Shape.subclass({\n  width: 1,\n  height: 1,\n});\n\nRect.prototype.setPath = function() {\n  var x = this.width / 2;\n  var y = this.height / 2;\n  /* eslint key-spacing: \"off\" */\n  this.path = [\n    { x: -x, y: -y },\n    { x:  x, y: -y },\n    { x:  x, y:  y },\n    { x: -x, y:  y },\n  ];\n};\n\nreturn Rect;\n\n} ) );\n/**\n * RoundedRect\n */\n\n( function( root, factory ) {\n  // module definition\n  if ( typeof module == 'object' && module.exports ) {\n    // CommonJS\n    module.exports = factory( require('./shape') );\n  } else {\n    // browser global\n    var Zdog = root.Zdog;\n    Zdog.RoundedRect = factory( Zdog.Shape );\n  }\n}( this, function factory( Shape ) {\n\nvar RoundedRect = Shape.subclass({\n  width: 1,\n  height: 1,\n  cornerRadius: 0.25,\n  closed: false,\n});\n\nRoundedRect.prototype.setPath = function() {\n  /* eslint\n     id-length: [ \"error\", { \"min\": 2, \"exceptions\": [ \"x\", \"y\" ] }],\n     key-spacing: \"off\" */\n  var xA = this.width / 2;\n  var yA = this.height / 2;\n  var shortSide = Math.min( xA, yA );\n  var cornerRadius = Math.min( this.cornerRadius, shortSide );\n  var xB = xA - cornerRadius;\n  var yB = yA - cornerRadius;\n  var path = [\n    // top right corner\n    { x: xB, y: -yA },\n    { arc: [\n      { x: xA, y: -yA },\n      { x: xA, y: -yB },\n    ] },\n  ];\n  // bottom right corner\n  if ( yB ) {\n    path.push({ x: xA, y: yB });\n  }\n  path.push({ arc: [\n    { x: xA, y:  yA },\n    { x: xB, y:  yA },\n  ] });\n  // bottom left corner\n  if ( xB ) {\n    path.push({ x: -xB, y: yA });\n  }\n  path.push({ arc: [\n    { x: -xA, y:  yA },\n    { x: -xA, y:  yB },\n  ] });\n  // top left corner\n  if ( yB ) {\n    path.push({ x: -xA, y: -yB });\n  }\n  path.push({ arc: [\n    { x: -xA, y: -yA },\n    { x: -xB, y: -yA },\n  ] });\n\n  // back to top right corner\n  if ( xB ) {\n    path.push({ x: xB, y: -yA });\n  }\n\n  this.path = path;\n};\n\nreturn RoundedRect;\n\n} ) );\n/**\n * Ellipse\n */\n\n( function( root, factory ) {\n  // module definition\n  if ( typeof module == 'object' && module.exports ) {\n    // CommonJS\n    module.exports = factory( require('./shape') );\n  } else {\n    // browser global\n    var Zdog = root.Zdog;\n    Zdog.Ellipse = factory( Zdog.Shape );\n  }\n\n}( this, function factory( Shape ) {\n\nvar Ellipse = Shape.subclass({\n  diameter: 1,\n  width: undefined,\n  height: undefined,\n  quarters: 4,\n  closed: false,\n});\n\nEllipse.prototype.setPath = function() {\n  var width = this.width != undefined ? this.width : this.diameter;\n  var height = this.height != undefined ? this.height : this.diameter;\n  var x = width/2;\n  var y = height/2;\n  this.path = [\n    { x: 0, y: -y },\n    { arc: [ // top right\n      { x: x, y: -y },\n      { x: x, y: 0 },\n    ] },\n  ];\n  // bottom right\n  if ( this.quarters > 1 ) {\n    this.path.push({ arc: [\n      { x: x, y: y },\n      { x: 0, y: y },\n    ] });\n  }\n  // bottom left\n  if ( this.quarters > 2 ) {\n    this.path.push({ arc: [\n      { x: -x, y: y },\n      { x: -x, y: 0 },\n    ] });\n  }\n  // top left\n  if ( this.quarters > 3 ) {\n    this.path.push({ arc: [\n      { x: -x, y: -y },\n      { x: 0, y: -y },\n    ] });\n  }\n};\n\nreturn Ellipse;\n\n} ) );\n/**\n * Shape\n */\n\n( function( root, factory ) {\n  // module definition\n  if ( typeof module == 'object' && module.exports ) {\n    // CommonJS\n    module.exports = factory( require('./boilerplate'), require('./shape') );\n  } else {\n    // browser global\n    var Zdog = root.Zdog;\n    Zdog.Polygon = factory( Zdog, Zdog.Shape );\n  }\n}( this, function factory( utils, Shape ) {\n\nvar Polygon = Shape.subclass({\n  sides: 3,\n  radius: 0.5,\n});\n\nvar TAU = utils.TAU;\n\nPolygon.prototype.setPath = function() {\n  this.path = [];\n  for ( var i = 0; i < this.sides; i++ ) {\n    var theta = i / this.sides * TAU - TAU/4;\n    var x = Math.cos( theta ) * this.radius;\n    var y = Math.sin( theta ) * this.radius;\n    this.path.push({ x: x, y: y });\n  }\n};\n\nreturn Polygon;\n\n} ) );\n/**\n * Hemisphere composite shape\n */\n\n( function( root, factory ) {\n  // module definition\n  if ( typeof module == 'object' && module.exports ) {\n    // CommonJS\n    module.exports = factory( require('./boilerplate'), require('./vector'),\n        require('./anchor'), require('./ellipse') );\n  } else {\n    // browser global\n    var Zdog = root.Zdog;\n    Zdog.Hemisphere = factory( Zdog, Zdog.Vector, Zdog.Anchor, Zdog.Ellipse );\n  }\n}( this, function factory( utils, Vector, Anchor, Ellipse ) {\n\nvar Hemisphere = Ellipse.subclass({\n  fill: true,\n});\n\nvar TAU = utils.TAU;\n\nHemisphere.prototype.create = function( /* options */) {\n  // call super\n  Ellipse.prototype.create.apply( this, arguments );\n  // composite shape, create child shapes\n  this.apex = new Anchor({\n    addTo: this,\n    translate: { z: this.diameter / 2 },\n  });\n  // vector used for calculation\n  this.renderCentroid = new Vector();\n};\n\nHemisphere.prototype.updateSortValue = function() {\n  // centroid of hemisphere is 3/8 between origin and apex\n  this.renderCentroid.set( this.renderOrigin )\n    .lerp( this.apex.renderOrigin, 3/8 );\n  this.sortValue = this.renderCentroid.z;\n};\n\nHemisphere.prototype.render = function( ctx, renderer ) {\n  this.renderDome( ctx, renderer );\n  // call super\n  Ellipse.prototype.render.apply( this, arguments );\n};\n\nHemisphere.prototype.renderDome = function( ctx, renderer ) {\n  if ( !this.visible ) {\n    return;\n  }\n  var elem = this.getDomeRenderElement( ctx, renderer );\n  var contourAngle = Math.atan2( this.renderNormal.y, this.renderNormal.x );\n  var domeRadius = this.diameter / 2 * this.renderNormal.magnitude();\n  var x = this.renderOrigin.x;\n  var y = this.renderOrigin.y;\n\n  if ( renderer.isCanvas ) {\n    // canvas\n    var startAngle = contourAngle + TAU/4;\n    var endAngle = contourAngle - TAU/4;\n    ctx.beginPath();\n    ctx.arc( x, y, domeRadius, startAngle, endAngle );\n  } else if ( renderer.isSvg ) {\n    // svg\n    contourAngle = ( contourAngle - TAU/4 ) / TAU * 360;\n    this.domeSvgElement.setAttribute( 'd', 'M ' + -domeRadius + ',0 A ' +\n        domeRadius + ',' + domeRadius + ' 0 0 1 ' + domeRadius + ',0' );\n    this.domeSvgElement.setAttribute( 'transform',\n        'translate(' + x + ',' + y + ' ) rotate(' + contourAngle + ')' );\n  }\n\n  renderer.stroke( ctx, elem, this.stroke, this.color, this.getLineWidth() );\n  renderer.fill( ctx, elem, this.fill, this.color );\n  renderer.end( ctx, elem );\n};\n\nvar svgURI = 'http://www.w3.org/2000/svg';\n\nHemisphere.prototype.getDomeRenderElement = function( ctx, renderer ) {\n  if ( !renderer.isSvg ) {\n    return;\n  }\n  if ( !this.domeSvgElement ) {\n    // create svgElement\n    this.domeSvgElement = document.createElementNS( svgURI, 'path' );\n    this.domeSvgElement.setAttribute( 'stroke-linecap', 'round' );\n    this.domeSvgElement.setAttribute( 'stroke-linejoin', 'round' );\n  }\n  return this.domeSvgElement;\n};\n\nreturn Hemisphere;\n\n} ) );\n/**\n * Cylinder composite shape\n */\n\n( function( root, factory ) {\n  // module definition\n  if ( typeof module == 'object' && module.exports ) {\n    // CommonJS\n    module.exports = factory( require('./boilerplate'),\n        require('./path-command'), require('./shape'), require('./group'),\n        require('./ellipse') );\n  } else {\n    // browser global\n    var Zdog = root.Zdog;\n    Zdog.Cylinder = factory( Zdog, Zdog.PathCommand, Zdog.Shape,\n        Zdog.Group, Zdog.Ellipse );\n  }\n}( this, function factory( utils, PathCommand, Shape, Group, Ellipse ) {\n\nfunction noop() {}\n\n// ----- CylinderGroup ----- //\n\nvar CylinderGroup = Group.subclass({\n  color: '#333',\n  updateSort: true,\n});\n\nCylinderGroup.prototype.create = function() {\n  Group.prototype.create.apply( this, arguments );\n  this.pathCommands = [\n    new PathCommand( 'move', [ {} ] ),\n    new PathCommand( 'line', [ {} ] ),\n  ];\n};\n\nCylinderGroup.prototype.render = function( ctx, renderer ) {\n  this.renderCylinderSurface( ctx, renderer );\n  Group.prototype.render.apply( this, arguments );\n};\n\nCylinderGroup.prototype.renderCylinderSurface = function( ctx, renderer ) {\n  if ( !this.visible ) {\n    return;\n  }\n  // render cylinder surface\n  var elem = this.getRenderElement( ctx, renderer );\n  var frontBase = this.frontBase;\n  var rearBase = this.rearBase;\n  var scale = frontBase.renderNormal.magnitude();\n  var strokeWidth = frontBase.diameter * scale + frontBase.getLineWidth();\n  // set path command render points\n  this.pathCommands[0].renderPoints[0].set( frontBase.renderOrigin );\n  this.pathCommands[1].renderPoints[0].set( rearBase.renderOrigin );\n\n  if ( renderer.isCanvas ) {\n    ctx.lineCap = 'butt'; // nice\n  }\n  renderer.renderPath( ctx, elem, this.pathCommands );\n  renderer.stroke( ctx, elem, true, this.color, strokeWidth );\n  renderer.end( ctx, elem );\n\n  if ( renderer.isCanvas ) {\n    ctx.lineCap = 'round'; // reset\n  }\n};\n\nvar svgURI = 'http://www.w3.org/2000/svg';\n\nCylinderGroup.prototype.getRenderElement = function( ctx, renderer ) {\n  if ( !renderer.isSvg ) {\n    return;\n  }\n  if ( !this.svgElement ) {\n    // create svgElement\n    this.svgElement = document.createElementNS( svgURI, 'path' );\n  }\n  return this.svgElement;\n};\n\n// prevent double-creation in parent.copyGraph()\n// only create in Cylinder.create()\nCylinderGroup.prototype.copyGraph = noop;\n\n// ----- CylinderEllipse ----- //\n\nvar CylinderEllipse = Ellipse.subclass();\n\nCylinderEllipse.prototype.copyGraph = noop;\n\n// ----- Cylinder ----- //\n\nvar Cylinder = Shape.subclass({\n  diameter: 1,\n  length: 1,\n  frontFace: undefined,\n  fill: true,\n});\n\nvar TAU = utils.TAU;\n\nCylinder.prototype.create = function( /* options */) {\n  // call super\n  Shape.prototype.create.apply( this, arguments );\n  // composite shape, create child shapes\n  // CylinderGroup to render cylinder surface then bases\n  this.group = new CylinderGroup({\n    addTo: this,\n    color: this.color,\n    visible: this.visible,\n  });\n  var baseZ = this.length / 2;\n  var baseColor = this.backface || true;\n  // front outside base\n  this.frontBase = this.group.frontBase = new Ellipse({\n    addTo: this.group,\n    diameter: this.diameter,\n    translate: { z: baseZ },\n    rotate: { y: TAU/2 },\n    color: this.color,\n    stroke: this.stroke,\n    fill: this.fill,\n    backface: this.frontFace || baseColor,\n    visible: this.visible,\n  });\n  // back outside base\n  this.rearBase = this.group.rearBase = this.frontBase.copy({\n    translate: { z: -baseZ },\n    rotate: { y: 0 },\n    backface: baseColor,\n  });\n};\n\n// Cylinder shape does not render anything\nCylinder.prototype.render = function() {};\n\n// ----- set child properties ----- //\n\nvar childProperties = [ 'stroke', 'fill', 'color', 'visible' ];\nchildProperties.forEach( function( property ) {\n  // use proxy property for custom getter & setter\n  var _prop = '_' + property;\n  Object.defineProperty( Cylinder.prototype, property, {\n    get: function() {\n      return this[ _prop ];\n    },\n    set: function( value ) {\n      this[ _prop ] = value;\n      // set property on children\n      if ( this.frontBase ) {\n        this.frontBase[ property ] = value;\n        this.rearBase[ property ] = value;\n        this.group[ property ] = value;\n      }\n    },\n  } );\n} );\n\n// TODO child property setter for backface, frontBaseColor, & rearBaseColor\n\nreturn Cylinder;\n\n} ) );\n/**\n * Cone composite shape\n */\n\n( function( root, factory ) {\n  // module definition\n  if ( typeof module == 'object' && module.exports ) {\n    // CommonJS\n    module.exports = factory( require('./boilerplate'), require('./vector'),\n        require('./path-command'), require('./anchor'), require('./ellipse') );\n  } else {\n    // browser global\n    var Zdog = root.Zdog;\n    Zdog.Cone = factory( Zdog, Zdog.Vector, Zdog.PathCommand,\n        Zdog.Anchor, Zdog.Ellipse );\n  }\n}( this, function factory( utils, Vector, PathCommand, Anchor, Ellipse ) {\n\nvar Cone = Ellipse.subclass({\n  length: 1,\n  fill: true,\n});\n\nvar TAU = utils.TAU;\n\nCone.prototype.create = function( /* options */) {\n  // call super\n  Ellipse.prototype.create.apply( this, arguments );\n  // composite shape, create child shapes\n  this.apex = new Anchor({\n    addTo: this,\n    translate: { z: this.length },\n  });\n\n  // vectors used for calculation\n  this.renderApex = new Vector();\n  this.renderCentroid = new Vector();\n  this.tangentA = new Vector();\n  this.tangentB = new Vector();\n\n  this.surfacePathCommands = [\n    new PathCommand( 'move', [ {} ] ), // points set in renderConeSurface\n    new PathCommand( 'line', [ {} ] ),\n    new PathCommand( 'line', [ {} ] ),\n  ];\n};\n\nCone.prototype.updateSortValue = function() {\n  // center of cone is one third of its length\n  this.renderCentroid.set( this.renderOrigin )\n    .lerp( this.apex.renderOrigin, 1/3 );\n  this.sortValue = this.renderCentroid.z;\n};\n\nCone.prototype.render = function( ctx, renderer ) {\n  this.renderConeSurface( ctx, renderer );\n  Ellipse.prototype.render.apply( this, arguments );\n};\n\nCone.prototype.renderConeSurface = function( ctx, renderer ) {\n  if ( !this.visible ) {\n    return;\n  }\n  this.renderApex.set( this.apex.renderOrigin )\n    .subtract( this.renderOrigin );\n\n  var scale = this.renderNormal.magnitude();\n  var apexDistance = this.renderApex.magnitude2d();\n  var normalDistance = this.renderNormal.magnitude2d();\n  // eccentricity\n  var eccenAngle = Math.acos( normalDistance/scale );\n  var eccen = Math.sin( eccenAngle );\n  var radius = this.diameter / 2 * scale;\n  // does apex extend beyond eclipse of face\n  var isApexVisible = radius * eccen < apexDistance;\n  if ( !isApexVisible ) {\n    return;\n  }\n  // update tangents\n  var apexAngle = Math.atan2( this.renderNormal.y, this.renderNormal.x ) +\n      TAU/2;\n  var projectLength = apexDistance/eccen;\n  var projectAngle = Math.acos( radius/projectLength );\n  // set tangent points\n  var tangentA = this.tangentA;\n  var tangentB = this.tangentB;\n\n  tangentA.x = Math.cos( projectAngle ) * radius * eccen;\n  tangentA.y = Math.sin( projectAngle ) * radius;\n\n  tangentB.set( this.tangentA );\n  tangentB.y *= -1;\n\n  tangentA.rotateZ( apexAngle );\n  tangentB.rotateZ( apexAngle );\n  tangentA.add( this.renderOrigin );\n  tangentB.add( this.renderOrigin );\n\n  this.setSurfaceRenderPoint( 0, tangentA );\n  this.setSurfaceRenderPoint( 1, this.apex.renderOrigin );\n  this.setSurfaceRenderPoint( 2, tangentB );\n\n  // render\n  var elem = this.getSurfaceRenderElement( ctx, renderer );\n  renderer.renderPath( ctx, elem, this.surfacePathCommands );\n  renderer.stroke( ctx, elem, this.stroke, this.color, this.getLineWidth() );\n  renderer.fill( ctx, elem, this.fill, this.color );\n  renderer.end( ctx, elem );\n};\n\nvar svgURI = 'http://www.w3.org/2000/svg';\n\nCone.prototype.getSurfaceRenderElement = function( ctx, renderer ) {\n  if ( !renderer.isSvg ) {\n    return;\n  }\n  if ( !this.surfaceSvgElement ) {\n    // create svgElement\n    this.surfaceSvgElement = document.createElementNS( svgURI, 'path' );\n    this.surfaceSvgElement.setAttribute( 'stroke-linecap', 'round' );\n    this.surfaceSvgElement.setAttribute( 'stroke-linejoin', 'round' );\n  }\n  return this.surfaceSvgElement;\n};\n\nCone.prototype.setSurfaceRenderPoint = function( index, point ) {\n  var renderPoint = this.surfacePathCommands[ index ].renderPoints[0];\n  renderPoint.set( point );\n};\n\nreturn Cone;\n\n} ) );\n/**\n * Box composite shape\n */\n\n( function( root, factory ) {\n  // module definition\n  if ( typeof module == 'object' && module.exports ) {\n    // CommonJS\n    module.exports = factory( require('./boilerplate'), require('./anchor'),\n        require('./shape'), require('./rect') );\n  } else {\n    // browser global\n    var Zdog = root.Zdog;\n    Zdog.Box = factory( Zdog, Zdog.Anchor, Zdog.Shape, Zdog.Rect );\n  }\n}( this, function factory( utils, Anchor, Shape, Rect ) {\n\n// ----- BoxRect ----- //\n\nvar BoxRect = Rect.subclass();\n// prevent double-creation in parent.copyGraph()\n// only create in Box.create()\nBoxRect.prototype.copyGraph = function() {};\n\n// ----- Box ----- //\n\nvar TAU = utils.TAU;\nvar faceNames = [\n  'frontFace',\n  'rearFace',\n  'leftFace',\n  'rightFace',\n  'topFace',\n  'bottomFace',\n];\n\nvar boxDefaults = utils.extend( {}, Shape.defaults );\ndelete boxDefaults.path;\nfaceNames.forEach( function( faceName ) {\n  boxDefaults[ faceName ] = true;\n} );\nutils.extend( boxDefaults, {\n  width: 1,\n  height: 1,\n  depth: 1,\n  fill: true,\n} );\n\nvar Box = Anchor.subclass( boxDefaults );\n\n/* eslint-disable no-self-assign */\nBox.prototype.create = function( options ) {\n  Anchor.prototype.create.call( this, options );\n  this.updatePath();\n  // HACK reset fill to trigger face setter\n  this.fill = this.fill;\n};\n\nBox.prototype.updatePath = function() {\n  // reset all faces to trigger setters\n  faceNames.forEach( function( faceName ) {\n    this[ faceName ] = this[ faceName ];\n  }, this );\n};\n/* eslint-enable no-self-assign */\n\nfaceNames.forEach( function( faceName ) {\n  var _faceName = '_' + faceName;\n  Object.defineProperty( Box.prototype, faceName, {\n    get: function() {\n      return this[ _faceName ];\n    },\n    set: function( value ) {\n      this[ _faceName ] = value;\n      this.setFace( faceName, value );\n    },\n  } );\n} );\n\nBox.prototype.setFace = function( faceName, value ) {\n  var rectProperty = faceName + 'Rect';\n  var rect = this[ rectProperty ];\n  // remove if false\n  if ( !value ) {\n    this.removeChild( rect );\n    return;\n  }\n  // update & add face\n  var options = this.getFaceOptions( faceName );\n  options.color = typeof value == 'string' ? value : this.color;\n\n  if ( rect ) {\n    // update previous\n    rect.setOptions( options );\n  } else {\n    // create new\n    rect = this[ rectProperty ] = new BoxRect( options );\n  }\n  rect.updatePath();\n  this.addChild( rect );\n};\n\nBox.prototype.getFaceOptions = function( faceName ) {\n  return {\n    frontFace: {\n      width: this.width,\n      height: this.height,\n      translate: { z: this.depth / 2 },\n    },\n    rearFace: {\n      width: this.width,\n      height: this.height,\n      translate: { z: -this.depth / 2 },\n      rotate: { y: TAU/2 },\n    },\n    leftFace: {\n      width: this.depth,\n      height: this.height,\n      translate: { x: -this.width / 2 },\n      rotate: { y: -TAU/4 },\n    },\n    rightFace: {\n      width: this.depth,\n      height: this.height,\n      translate: { x: this.width / 2 },\n      rotate: { y: TAU/4 },\n    },\n    topFace: {\n      width: this.width,\n      height: this.depth,\n      translate: { y: -this.height / 2 },\n      rotate: { x: -TAU/4 },\n    },\n    bottomFace: {\n      width: this.width,\n      height: this.depth,\n      translate: { y: this.height / 2 },\n      rotate: { x: TAU/4 },\n    },\n  }[ faceName ];\n};\n\n// ----- set face properties ----- //\n\nvar childProperties = [ 'color', 'stroke', 'fill', 'backface', 'front',\n  'visible' ];\nchildProperties.forEach( function( property ) {\n  // use proxy property for custom getter & setter\n  var _prop = '_' + property;\n  Object.defineProperty( Box.prototype, property, {\n    get: function() {\n      return this[ _prop ];\n    },\n    set: function( value ) {\n      this[ _prop ] = value;\n      faceNames.forEach( function( faceName ) {\n        var rect = this[ faceName + 'Rect' ];\n        var isFaceColor = typeof this[ faceName ] == 'string';\n        var isColorUnderwrite = property == 'color' && isFaceColor;\n        if ( rect && !isColorUnderwrite ) {\n          rect[ property ] = value;\n        }\n      }, this );\n    },\n  } );\n} );\n\nreturn Box;\n\n} ) );\n/**\n * Index\n */\n\n( function( root, factory ) {\n  // module definition\n  if ( typeof module == 'object' && module.exports ) {\n    // CommonJS\n    module.exports = factory(\n        require('./boilerplate'),\n        require('./canvas-renderer'),\n        require('./svg-renderer'),\n        require('./vector'),\n        require('./anchor'),\n        require('./dragger'),\n        require('./illustration'),\n        require('./path-command'),\n        require('./shape'),\n        require('./group'),\n        require('./rect'),\n        require('./rounded-rect'),\n        require('./ellipse'),\n        require('./polygon'),\n        require('./hemisphere'),\n        require('./cylinder'),\n        require('./cone'),\n        require('./box')\n    );\n  } else if ( typeof define == 'function' && define.amd ) {\n    /* globals define */ // AMD\n    define( 'zdog', [], root.Zdog );\n  }\n/* eslint-disable max-params */\n} )( this, function factory( Zdog, CanvasRenderer, SvgRenderer, Vector, Anchor,\n    Dragger, Illustration, PathCommand, Shape, Group, Rect, RoundedRect,\n    Ellipse, Polygon, Hemisphere, Cylinder, Cone, Box ) {\n/* eslint-enable max-params */\n\n      Zdog.CanvasRenderer = CanvasRenderer;\n      Zdog.SvgRenderer = SvgRenderer;\n      Zdog.Vector = Vector;\n      Zdog.Anchor = Anchor;\n      Zdog.Dragger = Dragger;\n      Zdog.Illustration = Illustration;\n      Zdog.PathCommand = PathCommand;\n      Zdog.Shape = Shape;\n      Zdog.Group = Group;\n      Zdog.Rect = Rect;\n      Zdog.RoundedRect = RoundedRect;\n      Zdog.Ellipse = Ellipse;\n      Zdog.Polygon = Polygon;\n      Zdog.Hemisphere = Hemisphere;\n      Zdog.Cylinder = Cylinder;\n      Zdog.Cone = Cone;\n      Zdog.Box = Box;\n\n      return Zdog;\n} );\n"
  },
  {
    "path": "js/anchor.js",
    "content": "/**\n * Anchor\n */\n\n( function( root, factory ) {\n  // module definition\n  if ( typeof module == 'object' && module.exports ) {\n    // CommonJS\n    module.exports = factory( require('./boilerplate'), require('./vector'),\n        require('./canvas-renderer'), require('./svg-renderer') );\n  } else {\n    // browser global\n    var Zdog = root.Zdog;\n    Zdog.Anchor = factory( Zdog, Zdog.Vector, Zdog.CanvasRenderer,\n        Zdog.SvgRenderer );\n  }\n}( this, function factory( utils, Vector, CanvasRenderer, SvgRenderer ) {\n\nvar TAU = utils.TAU;\nvar onePoint = { x: 1, y: 1, z: 1 };\n\nfunction Anchor( options ) {\n  this.create( options || {} );\n}\n\nAnchor.prototype.create = function( options ) {\n  this.children = [];\n  // set defaults & options\n  utils.extend( this, this.constructor.defaults );\n  this.setOptions( options );\n\n  // transform\n  this.translate = new Vector( options.translate );\n  this.rotate = new Vector( options.rotate );\n  this.scale = new Vector( onePoint ).multiply( this.scale );\n  // origin\n  this.origin = new Vector();\n  this.renderOrigin = new Vector();\n\n  if ( this.addTo ) {\n    this.addTo.addChild( this );\n  }\n};\n\nAnchor.defaults = {};\n\nAnchor.optionKeys = Object.keys( Anchor.defaults ).concat([\n  'rotate',\n  'translate',\n  'scale',\n  'addTo',\n]);\n\nAnchor.prototype.setOptions = function( options ) {\n  var optionKeys = this.constructor.optionKeys;\n\n  for ( var key in options ) {\n    if ( optionKeys.indexOf( key ) != -1 ) {\n      this[ key ] = options[ key ];\n    }\n  }\n};\n\nAnchor.prototype.addChild = function( shape ) {\n  if ( this.children.indexOf( shape ) != -1 ) {\n    return;\n  }\n  shape.remove(); // remove previous parent\n  shape.addTo = this; // keep parent reference\n  this.children.push( shape );\n};\n\nAnchor.prototype.removeChild = function( shape ) {\n  var index = this.children.indexOf( shape );\n  if ( index != -1 ) {\n    this.children.splice( index, 1 );\n  }\n};\n\nAnchor.prototype.remove = function() {\n  if ( this.addTo ) {\n    this.addTo.removeChild( this );\n  }\n};\n\n// ----- update ----- //\n\nAnchor.prototype.update = function() {\n  // update self\n  this.reset();\n  // update children\n  this.children.forEach( function( child ) {\n    child.update();\n  } );\n  this.transform( this.translate, this.rotate, this.scale );\n};\n\nAnchor.prototype.reset = function() {\n  this.renderOrigin.set( this.origin );\n};\n\nAnchor.prototype.transform = function( translation, rotation, scale ) {\n  this.renderOrigin.transform( translation, rotation, scale );\n  // transform children\n  this.children.forEach( function( child ) {\n    child.transform( translation, rotation, scale );\n  } );\n};\n\nAnchor.prototype.updateGraph = function() {\n  this.update();\n  this.updateFlatGraph();\n  this.flatGraph.forEach( function( item ) {\n    item.updateSortValue();\n  } );\n  // z-sort\n  this.flatGraph.sort( Anchor.shapeSorter );\n};\n\nAnchor.shapeSorter = function( a, b ) {\n  return a.sortValue - b.sortValue;\n};\n\n// custom getter to check for flatGraph before using it\nObject.defineProperty( Anchor.prototype, 'flatGraph', {\n  get: function() {\n    if ( !this._flatGraph ) {\n      this.updateFlatGraph();\n    }\n    return this._flatGraph;\n  },\n  set: function( graph ) {\n    this._flatGraph = graph;\n  },\n} );\n\nAnchor.prototype.updateFlatGraph = function() {\n  this.flatGraph = this.getFlatGraph();\n};\n\n// return Array of self & all child graph items\nAnchor.prototype.getFlatGraph = function() {\n  var flatGraph = [ this ];\n  return this.addChildFlatGraph( flatGraph );\n};\n\nAnchor.prototype.addChildFlatGraph = function( flatGraph ) {\n  this.children.forEach( function( child ) {\n    var childFlatGraph = child.getFlatGraph();\n    Array.prototype.push.apply( flatGraph, childFlatGraph );\n  } );\n  return flatGraph;\n};\n\nAnchor.prototype.updateSortValue = function() {\n  this.sortValue = this.renderOrigin.z;\n};\n\n// ----- render ----- //\n\nAnchor.prototype.render = function() {};\n\n// TODO refactor out CanvasRenderer so its not a dependency within anchor.js\nAnchor.prototype.renderGraphCanvas = function( ctx ) {\n  if ( !ctx ) {\n    throw new Error( 'ctx is ' + ctx + '. ' +\n      'Canvas context required for render. Check .renderGraphCanvas( ctx ).' );\n  }\n  this.flatGraph.forEach( function( item ) {\n    item.render( ctx, CanvasRenderer );\n  } );\n};\n\nAnchor.prototype.renderGraphSvg = function( svg ) {\n  if ( !svg ) {\n    throw new Error( 'svg is ' + svg + '. ' +\n      'SVG required for render. Check .renderGraphSvg( svg ).' );\n  }\n  this.flatGraph.forEach( function( item ) {\n    item.render( svg, SvgRenderer );\n  } );\n};\n\n// ----- misc ----- //\n\nAnchor.prototype.copy = function( options ) {\n  // copy options\n  var itemOptions = {};\n  var optionKeys = this.constructor.optionKeys;\n  optionKeys.forEach( function( key ) {\n    itemOptions[ key ] = this[ key ];\n  }, this );\n  // add set options\n  utils.extend( itemOptions, options );\n  var ItemClass = this.constructor;\n  return new ItemClass( itemOptions );\n};\n\nAnchor.prototype.copyGraph = function( options ) {\n  var clone = this.copy( options );\n  this.children.forEach( function( child ) {\n    child.copyGraph({\n      addTo: clone,\n    });\n  } );\n  return clone;\n};\n\nAnchor.prototype.normalizeRotate = function() {\n  this.rotate.x = utils.modulo( this.rotate.x, TAU );\n  this.rotate.y = utils.modulo( this.rotate.y, TAU );\n  this.rotate.z = utils.modulo( this.rotate.z, TAU );\n};\n\n// ----- subclass ----- //\n\nfunction getSubclass( Super ) {\n  return function( defaults ) {\n    // create constructor\n    function Item( options ) {\n      this.create( options || {} );\n    }\n\n    Item.prototype = Object.create( Super.prototype );\n    Item.prototype.constructor = Item;\n\n    Item.defaults = utils.extend( {}, Super.defaults );\n    utils.extend( Item.defaults, defaults );\n    // create optionKeys\n    Item.optionKeys = Super.optionKeys.slice( 0 );\n    // add defaults keys to optionKeys, dedupe\n    Object.keys( Item.defaults ).forEach( function( key ) {\n      if ( !Item.optionKeys.indexOf( key ) != 1 ) {\n        Item.optionKeys.push( key );\n      }\n    } );\n\n    Item.subclass = getSubclass( Item );\n\n    return Item;\n  };\n}\n\nAnchor.subclass = getSubclass( Anchor );\n\nreturn Anchor;\n\n} ) );\n"
  },
  {
    "path": "js/boilerplate.js",
    "content": "/*!\n * Zdog v1.1.3\n * Round, flat, designer-friendly pseudo-3D engine\n * Licensed MIT\n * https://zzz.dog\n * Copyright 2020 Metafizzy\n */\n\n/**\n * Boilerplate & utils\n */\n\n( function( root, factory ) {\n  // module definition\n  if ( typeof module == 'object' && module.exports ) {\n    // CommonJS\n    module.exports = factory();\n  } else {\n    // browser global\n    root.Zdog = factory();\n  }\n}( this, function factory() {\n\nvar Zdog = {};\n\nZdog.TAU = Math.PI * 2;\n\nZdog.extend = function( a, b ) {\n  for ( var prop in b ) {\n    a[ prop ] = b[ prop ];\n  }\n  return a;\n};\n\nZdog.lerp = function( a, b, alpha ) {\n  return ( b - a ) * alpha + a;\n};\n\nZdog.modulo = function( num, div ) {\n  return ( ( num % div ) + div ) % div;\n};\n\nvar powerMultipliers = {\n  2: function( a ) {\n    return a * a;\n  },\n  3: function( a ) {\n    return a * a * a;\n  },\n  4: function( a ) {\n    return a * a * a * a;\n  },\n  5: function( a ) {\n    return a * a * a * a * a;\n  },\n};\n\nZdog.easeInOut = function( alpha, power ) {\n  if ( power == 1 ) {\n    return alpha;\n  }\n  alpha = Math.max( 0, Math.min( 1, alpha ) );\n  var isFirstHalf = alpha < 0.5;\n  var slope = isFirstHalf ? alpha : 1 - alpha;\n  slope /= 0.5;\n  // make easing steeper with more multiples\n  var powerMultiplier = powerMultipliers[ power ] || powerMultipliers[2];\n  var curve = powerMultiplier( slope );\n  curve /= 2;\n  return isFirstHalf ? curve : 1 - curve;\n};\n\nreturn Zdog;\n\n} ) );\n"
  },
  {
    "path": "js/box.js",
    "content": "/**\n * Box composite shape\n */\n\n( function( root, factory ) {\n  // module definition\n  if ( typeof module == 'object' && module.exports ) {\n    // CommonJS\n    module.exports = factory( require('./boilerplate'), require('./anchor'),\n        require('./shape'), require('./rect') );\n  } else {\n    // browser global\n    var Zdog = root.Zdog;\n    Zdog.Box = factory( Zdog, Zdog.Anchor, Zdog.Shape, Zdog.Rect );\n  }\n}( this, function factory( utils, Anchor, Shape, Rect ) {\n\n// ----- BoxRect ----- //\n\nvar BoxRect = Rect.subclass();\n// prevent double-creation in parent.copyGraph()\n// only create in Box.create()\nBoxRect.prototype.copyGraph = function() {};\n\n// ----- Box ----- //\n\nvar TAU = utils.TAU;\nvar faceNames = [\n  'frontFace',\n  'rearFace',\n  'leftFace',\n  'rightFace',\n  'topFace',\n  'bottomFace',\n];\n\nvar boxDefaults = utils.extend( {}, Shape.defaults );\ndelete boxDefaults.path;\nfaceNames.forEach( function( faceName ) {\n  boxDefaults[ faceName ] = true;\n} );\nutils.extend( boxDefaults, {\n  width: 1,\n  height: 1,\n  depth: 1,\n  fill: true,\n} );\n\nvar Box = Anchor.subclass( boxDefaults );\n\n/* eslint-disable no-self-assign */\nBox.prototype.create = function( options ) {\n  Anchor.prototype.create.call( this, options );\n  this.updatePath();\n  // HACK reset fill to trigger face setter\n  this.fill = this.fill;\n};\n\nBox.prototype.updatePath = function() {\n  // reset all faces to trigger setters\n  faceNames.forEach( function( faceName ) {\n    this[ faceName ] = this[ faceName ];\n  }, this );\n};\n/* eslint-enable no-self-assign */\n\nfaceNames.forEach( function( faceName ) {\n  var _faceName = '_' + faceName;\n  Object.defineProperty( Box.prototype, faceName, {\n    get: function() {\n      return this[ _faceName ];\n    },\n    set: function( value ) {\n      this[ _faceName ] = value;\n      this.setFace( faceName, value );\n    },\n  } );\n} );\n\nBox.prototype.setFace = function( faceName, value ) {\n  var rectProperty = faceName + 'Rect';\n  var rect = this[ rectProperty ];\n  // remove if false\n  if ( !value ) {\n    this.removeChild( rect );\n    return;\n  }\n  // update & add face\n  var options = this.getFaceOptions( faceName );\n  options.color = typeof value == 'string' ? value : this.color;\n\n  if ( rect ) {\n    // update previous\n    rect.setOptions( options );\n  } else {\n    // create new\n    rect = this[ rectProperty ] = new BoxRect( options );\n  }\n  rect.updatePath();\n  this.addChild( rect );\n};\n\nBox.prototype.getFaceOptions = function( faceName ) {\n  return {\n    frontFace: {\n      width: this.width,\n      height: this.height,\n      translate: { z: this.depth / 2 },\n    },\n    rearFace: {\n      width: this.width,\n      height: this.height,\n      translate: { z: -this.depth / 2 },\n      rotate: { y: TAU/2 },\n    },\n    leftFace: {\n      width: this.depth,\n      height: this.height,\n      translate: { x: -this.width / 2 },\n      rotate: { y: -TAU/4 },\n    },\n    rightFace: {\n      width: this.depth,\n      height: this.height,\n      translate: { x: this.width / 2 },\n      rotate: { y: TAU/4 },\n    },\n    topFace: {\n      width: this.width,\n      height: this.depth,\n      translate: { y: -this.height / 2 },\n      rotate: { x: -TAU/4 },\n    },\n    bottomFace: {\n      width: this.width,\n      height: this.depth,\n      translate: { y: this.height / 2 },\n      rotate: { x: TAU/4 },\n    },\n  }[ faceName ];\n};\n\n// ----- set face properties ----- //\n\nvar childProperties = [ 'color', 'stroke', 'fill', 'backface', 'front',\n  'visible' ];\nchildProperties.forEach( function( property ) {\n  // use proxy property for custom getter & setter\n  var _prop = '_' + property;\n  Object.defineProperty( Box.prototype, property, {\n    get: function() {\n      return this[ _prop ];\n    },\n    set: function( value ) {\n      this[ _prop ] = value;\n      faceNames.forEach( function( faceName ) {\n        var rect = this[ faceName + 'Rect' ];\n        var isFaceColor = typeof this[ faceName ] == 'string';\n        var isColorUnderwrite = property == 'color' && isFaceColor;\n        if ( rect && !isColorUnderwrite ) {\n          rect[ property ] = value;\n        }\n      }, this );\n    },\n  } );\n} );\n\nreturn Box;\n\n} ) );\n"
  },
  {
    "path": "js/canvas-renderer.js",
    "content": "/**\n * CanvasRenderer\n */\n\n( function( root, factory ) {\n  // module definition\n  if ( typeof module == 'object' && module.exports ) {\n    // CommonJS\n    module.exports = factory();\n  } else {\n    // browser global\n    root.Zdog.CanvasRenderer = factory();\n  }\n}( this, function factory() {\n\nvar CanvasRenderer = { isCanvas: true };\n\nCanvasRenderer.begin = function( ctx ) {\n  ctx.beginPath();\n};\n\nCanvasRenderer.move = function( ctx, elem, point ) {\n  ctx.moveTo( point.x, point.y );\n};\n\nCanvasRenderer.line = function( ctx, elem, point ) {\n  ctx.lineTo( point.x, point.y );\n};\n\nCanvasRenderer.bezier = function( ctx, elem, cp0, cp1, end ) {\n  ctx.bezierCurveTo( cp0.x, cp0.y, cp1.x, cp1.y, end.x, end.y );\n};\n\nCanvasRenderer.closePath = function( ctx ) {\n  ctx.closePath();\n};\n\nCanvasRenderer.setPath = function() {};\n\nCanvasRenderer.renderPath = function( ctx, elem, pathCommands, isClosed ) {\n  this.begin( ctx, elem );\n  pathCommands.forEach( function( command ) {\n    command.render( ctx, elem, CanvasRenderer );\n  } );\n  if ( isClosed ) {\n    this.closePath( ctx, elem );\n  }\n};\n\nCanvasRenderer.stroke = function( ctx, elem, isStroke, color, lineWidth ) {\n  if ( !isStroke ) {\n    return;\n  }\n  ctx.strokeStyle = color;\n  ctx.lineWidth = lineWidth;\n  ctx.stroke();\n};\n\nCanvasRenderer.fill = function( ctx, elem, isFill, color ) {\n  if ( !isFill ) {\n    return;\n  }\n  ctx.fillStyle = color;\n  ctx.fill();\n};\n\nCanvasRenderer.end = function() {};\n\nreturn CanvasRenderer;\n\n} ) );\n"
  },
  {
    "path": "js/cone.js",
    "content": "/**\n * Cone composite shape\n */\n\n( function( root, factory ) {\n  // module definition\n  if ( typeof module == 'object' && module.exports ) {\n    // CommonJS\n    module.exports = factory( require('./boilerplate'), require('./vector'),\n        require('./path-command'), require('./anchor'), require('./ellipse') );\n  } else {\n    // browser global\n    var Zdog = root.Zdog;\n    Zdog.Cone = factory( Zdog, Zdog.Vector, Zdog.PathCommand,\n        Zdog.Anchor, Zdog.Ellipse );\n  }\n}( this, function factory( utils, Vector, PathCommand, Anchor, Ellipse ) {\n\nvar Cone = Ellipse.subclass({\n  length: 1,\n  fill: true,\n});\n\nvar TAU = utils.TAU;\n\nCone.prototype.create = function( /* options */) {\n  // call super\n  Ellipse.prototype.create.apply( this, arguments );\n  // composite shape, create child shapes\n  this.apex = new Anchor({\n    addTo: this,\n    translate: { z: this.length },\n  });\n\n  // vectors used for calculation\n  this.renderApex = new Vector();\n  this.renderCentroid = new Vector();\n  this.tangentA = new Vector();\n  this.tangentB = new Vector();\n\n  this.surfacePathCommands = [\n    new PathCommand( 'move', [ {} ] ), // points set in renderConeSurface\n    new PathCommand( 'line', [ {} ] ),\n    new PathCommand( 'line', [ {} ] ),\n  ];\n};\n\nCone.prototype.updateSortValue = function() {\n  // center of cone is one third of its length\n  this.renderCentroid.set( this.renderOrigin )\n    .lerp( this.apex.renderOrigin, 1/3 );\n  this.sortValue = this.renderCentroid.z;\n};\n\nCone.prototype.render = function( ctx, renderer ) {\n  this.renderConeSurface( ctx, renderer );\n  Ellipse.prototype.render.apply( this, arguments );\n};\n\nCone.prototype.renderConeSurface = function( ctx, renderer ) {\n  if ( !this.visible ) {\n    return;\n  }\n  this.renderApex.set( this.apex.renderOrigin )\n    .subtract( this.renderOrigin );\n\n  var scale = this.renderNormal.magnitude();\n  var apexDistance = this.renderApex.magnitude2d();\n  var normalDistance = this.renderNormal.magnitude2d();\n  // eccentricity\n  var eccenAngle = Math.acos( normalDistance/scale );\n  var eccen = Math.sin( eccenAngle );\n  var radius = this.diameter / 2 * scale;\n  // does apex extend beyond eclipse of face\n  var isApexVisible = radius * eccen < apexDistance;\n  if ( !isApexVisible ) {\n    return;\n  }\n  // update tangents\n  var apexAngle = Math.atan2( this.renderNormal.y, this.renderNormal.x ) +\n      TAU/2;\n  var projectLength = apexDistance/eccen;\n  var projectAngle = Math.acos( radius/projectLength );\n  // set tangent points\n  var tangentA = this.tangentA;\n  var tangentB = this.tangentB;\n\n  tangentA.x = Math.cos( projectAngle ) * radius * eccen;\n  tangentA.y = Math.sin( projectAngle ) * radius;\n\n  tangentB.set( this.tangentA );\n  tangentB.y *= -1;\n\n  tangentA.rotateZ( apexAngle );\n  tangentB.rotateZ( apexAngle );\n  tangentA.add( this.renderOrigin );\n  tangentB.add( this.renderOrigin );\n\n  this.setSurfaceRenderPoint( 0, tangentA );\n  this.setSurfaceRenderPoint( 1, this.apex.renderOrigin );\n  this.setSurfaceRenderPoint( 2, tangentB );\n\n  // render\n  var elem = this.getSurfaceRenderElement( ctx, renderer );\n  renderer.renderPath( ctx, elem, this.surfacePathCommands );\n  renderer.stroke( ctx, elem, this.stroke, this.color, this.getLineWidth() );\n  renderer.fill( ctx, elem, this.fill, this.color );\n  renderer.end( ctx, elem );\n};\n\nvar svgURI = 'http://www.w3.org/2000/svg';\n\nCone.prototype.getSurfaceRenderElement = function( ctx, renderer ) {\n  if ( !renderer.isSvg ) {\n    return;\n  }\n  if ( !this.surfaceSvgElement ) {\n    // create svgElement\n    this.surfaceSvgElement = document.createElementNS( svgURI, 'path' );\n    this.surfaceSvgElement.setAttribute( 'stroke-linecap', 'round' );\n    this.surfaceSvgElement.setAttribute( 'stroke-linejoin', 'round' );\n  }\n  return this.surfaceSvgElement;\n};\n\nCone.prototype.setSurfaceRenderPoint = function( index, point ) {\n  var renderPoint = this.surfacePathCommands[ index ].renderPoints[0];\n  renderPoint.set( point );\n};\n\nreturn Cone;\n\n} ) );\n"
  },
  {
    "path": "js/cylinder.js",
    "content": "/**\n * Cylinder composite shape\n */\n\n( function( root, factory ) {\n  // module definition\n  if ( typeof module == 'object' && module.exports ) {\n    // CommonJS\n    module.exports = factory( require('./boilerplate'),\n        require('./path-command'), require('./shape'), require('./group'),\n        require('./ellipse') );\n  } else {\n    // browser global\n    var Zdog = root.Zdog;\n    Zdog.Cylinder = factory( Zdog, Zdog.PathCommand, Zdog.Shape,\n        Zdog.Group, Zdog.Ellipse );\n  }\n}( this, function factory( utils, PathCommand, Shape, Group, Ellipse ) {\n\nfunction noop() {}\n\n// ----- CylinderGroup ----- //\n\nvar CylinderGroup = Group.subclass({\n  color: '#333',\n  updateSort: true,\n});\n\nCylinderGroup.prototype.create = function() {\n  Group.prototype.create.apply( this, arguments );\n  this.pathCommands = [\n    new PathCommand( 'move', [ {} ] ),\n    new PathCommand( 'line', [ {} ] ),\n  ];\n};\n\nCylinderGroup.prototype.render = function( ctx, renderer ) {\n  this.renderCylinderSurface( ctx, renderer );\n  Group.prototype.render.apply( this, arguments );\n};\n\nCylinderGroup.prototype.renderCylinderSurface = function( ctx, renderer ) {\n  if ( !this.visible ) {\n    return;\n  }\n  // render cylinder surface\n  var elem = this.getRenderElement( ctx, renderer );\n  var frontBase = this.frontBase;\n  var rearBase = this.rearBase;\n  var scale = frontBase.renderNormal.magnitude();\n  var strokeWidth = frontBase.diameter * scale + frontBase.getLineWidth();\n  // set path command render points\n  this.pathCommands[0].renderPoints[0].set( frontBase.renderOrigin );\n  this.pathCommands[1].renderPoints[0].set( rearBase.renderOrigin );\n\n  if ( renderer.isCanvas ) {\n    ctx.lineCap = 'butt'; // nice\n  }\n  renderer.renderPath( ctx, elem, this.pathCommands );\n  renderer.stroke( ctx, elem, true, this.color, strokeWidth );\n  renderer.end( ctx, elem );\n\n  if ( renderer.isCanvas ) {\n    ctx.lineCap = 'round'; // reset\n  }\n};\n\nvar svgURI = 'http://www.w3.org/2000/svg';\n\nCylinderGroup.prototype.getRenderElement = function( ctx, renderer ) {\n  if ( !renderer.isSvg ) {\n    return;\n  }\n  if ( !this.svgElement ) {\n    // create svgElement\n    this.svgElement = document.createElementNS( svgURI, 'path' );\n  }\n  return this.svgElement;\n};\n\n// prevent double-creation in parent.copyGraph()\n// only create in Cylinder.create()\nCylinderGroup.prototype.copyGraph = noop;\n\n// ----- CylinderEllipse ----- //\n\nvar CylinderEllipse = Ellipse.subclass();\n\nCylinderEllipse.prototype.copyGraph = noop;\n\n// ----- Cylinder ----- //\n\nvar Cylinder = Shape.subclass({\n  diameter: 1,\n  length: 1,\n  frontFace: undefined,\n  fill: true,\n});\n\nvar TAU = utils.TAU;\n\nCylinder.prototype.create = function( /* options */) {\n  // call super\n  Shape.prototype.create.apply( this, arguments );\n  // composite shape, create child shapes\n  // CylinderGroup to render cylinder surface then bases\n  this.group = new CylinderGroup({\n    addTo: this,\n    color: this.color,\n    visible: this.visible,\n  });\n  var baseZ = this.length / 2;\n  var baseColor = this.backface || true;\n  // front outside base\n  this.frontBase = this.group.frontBase = new Ellipse({\n    addTo: this.group,\n    diameter: this.diameter,\n    translate: { z: baseZ },\n    rotate: { y: TAU/2 },\n    color: this.color,\n    stroke: this.stroke,\n    fill: this.fill,\n    backface: this.frontFace || baseColor,\n    visible: this.visible,\n  });\n  // back outside base\n  this.rearBase = this.group.rearBase = this.frontBase.copy({\n    translate: { z: -baseZ },\n    rotate: { y: 0 },\n    backface: baseColor,\n  });\n};\n\n// Cylinder shape does not render anything\nCylinder.prototype.render = function() {};\n\n// ----- set child properties ----- //\n\nvar childProperties = [ 'stroke', 'fill', 'color', 'visible' ];\nchildProperties.forEach( function( property ) {\n  // use proxy property for custom getter & setter\n  var _prop = '_' + property;\n  Object.defineProperty( Cylinder.prototype, property, {\n    get: function() {\n      return this[ _prop ];\n    },\n    set: function( value ) {\n      this[ _prop ] = value;\n      // set property on children\n      if ( this.frontBase ) {\n        this.frontBase[ property ] = value;\n        this.rearBase[ property ] = value;\n        this.group[ property ] = value;\n      }\n    },\n  } );\n} );\n\n// TODO child property setter for backface, frontBaseColor, & rearBaseColor\n\nreturn Cylinder;\n\n} ) );\n"
  },
  {
    "path": "js/dragger.js",
    "content": "/**\n * Dragger\n */\n\n( function( root, factory ) {\n  // module definition\n  if ( typeof module == 'object' && module.exports ) {\n    // CommonJS\n    module.exports = factory();\n  } else {\n    // browser global\n    root.Zdog.Dragger = factory();\n  }\n}( this, function factory() {\n\n// quick & dirty drag event stuff\n// messes up if multiple pointers/touches\n\n// check for browser window #85\nvar hasWindow = typeof window != 'undefined';\n// event support, default to mouse events\nvar downEvent = 'mousedown';\nvar moveEvent = 'mousemove';\nvar upEvent = 'mouseup';\nif ( hasWindow ) {\n  if ( window.PointerEvent ) {\n    // PointerEvent, Chrome\n    downEvent = 'pointerdown';\n    moveEvent = 'pointermove';\n    upEvent = 'pointerup';\n  } else if ( 'ontouchstart' in window ) {\n    // Touch Events, iOS Safari\n    downEvent = 'touchstart';\n    moveEvent = 'touchmove';\n    upEvent = 'touchend';\n  }\n}\n\nfunction noop() {}\n\nfunction Dragger( options ) {\n  this.create( options || {} );\n}\n\nDragger.prototype.create = function( options ) {\n  this.onDragStart = options.onDragStart || noop;\n  this.onDragMove = options.onDragMove || noop;\n  this.onDragEnd = options.onDragEnd || noop;\n\n  this.bindDrag( options.startElement );\n};\n\nDragger.prototype.bindDrag = function( element ) {\n  element = this.getQueryElement( element );\n  if ( !element ) {\n    return;\n  }\n  // disable browser gestures #53\n  element.style.touchAction = 'none';\n  element.addEventListener( downEvent, this );\n};\n\nDragger.prototype.getQueryElement = function( element ) {\n  if ( typeof element == 'string' ) {\n    // with string, query selector\n    element = document.querySelector( element );\n  }\n  return element;\n};\n\nDragger.prototype.handleEvent = function( event ) {\n  var method = this[ 'on' + event.type ];\n  if ( method ) {\n    method.call( this, event );\n  }\n};\n\nDragger.prototype.onmousedown =\nDragger.prototype.onpointerdown = function( event ) {\n  this.dragStart( event, event );\n};\n\nDragger.prototype.ontouchstart = function( event ) {\n  this.dragStart( event, event.changedTouches[0] );\n};\n\nDragger.prototype.dragStart = function( event, pointer ) {\n  event.preventDefault();\n  this.dragStartX = pointer.pageX;\n  this.dragStartY = pointer.pageY;\n  if ( hasWindow ) {\n    window.addEventListener( moveEvent, this );\n    window.addEventListener( upEvent, this );\n  }\n  this.onDragStart( pointer );\n};\n\nDragger.prototype.ontouchmove = function( event ) {\n  // HACK, moved touch may not be first\n  this.dragMove( event, event.changedTouches[0] );\n};\n\nDragger.prototype.onmousemove =\nDragger.prototype.onpointermove = function( event ) {\n  this.dragMove( event, event );\n};\n\nDragger.prototype.dragMove = function( event, pointer ) {\n  event.preventDefault();\n  var moveX = pointer.pageX - this.dragStartX;\n  var moveY = pointer.pageY - this.dragStartY;\n  this.onDragMove( pointer, moveX, moveY );\n};\n\nDragger.prototype.onmouseup =\nDragger.prototype.onpointerup =\nDragger.prototype.ontouchend =\nDragger.prototype.dragEnd = function( /* event */) {\n  window.removeEventListener( moveEvent, this );\n  window.removeEventListener( upEvent, this );\n  this.onDragEnd();\n};\n\nreturn Dragger;\n\n} ) );\n"
  },
  {
    "path": "js/ellipse.js",
    "content": "/**\n * Ellipse\n */\n\n( function( root, factory ) {\n  // module definition\n  if ( typeof module == 'object' && module.exports ) {\n    // CommonJS\n    module.exports = factory( require('./shape') );\n  } else {\n    // browser global\n    var Zdog = root.Zdog;\n    Zdog.Ellipse = factory( Zdog.Shape );\n  }\n\n}( this, function factory( Shape ) {\n\nvar Ellipse = Shape.subclass({\n  diameter: 1,\n  width: undefined,\n  height: undefined,\n  quarters: 4,\n  closed: false,\n});\n\nEllipse.prototype.setPath = function() {\n  var width = this.width != undefined ? this.width : this.diameter;\n  var height = this.height != undefined ? this.height : this.diameter;\n  var x = width/2;\n  var y = height/2;\n  this.path = [\n    { x: 0, y: -y },\n    { arc: [ // top right\n      { x: x, y: -y },\n      { x: x, y: 0 },\n    ] },\n  ];\n  // bottom right\n  if ( this.quarters > 1 ) {\n    this.path.push({ arc: [\n      { x: x, y: y },\n      { x: 0, y: y },\n    ] });\n  }\n  // bottom left\n  if ( this.quarters > 2 ) {\n    this.path.push({ arc: [\n      { x: -x, y: y },\n      { x: -x, y: 0 },\n    ] });\n  }\n  // top left\n  if ( this.quarters > 3 ) {\n    this.path.push({ arc: [\n      { x: -x, y: -y },\n      { x: 0, y: -y },\n    ] });\n  }\n};\n\nreturn Ellipse;\n\n} ) );\n"
  },
  {
    "path": "js/group.js",
    "content": "/**\n * Group\n */\n\n( function( root, factory ) {\n  // module definition\n  if ( typeof module == 'object' && module.exports ) {\n    // CommonJS\n    module.exports = factory( require('./anchor') );\n  } else {\n    // browser global\n    var Zdog = root.Zdog;\n    Zdog.Group = factory( Zdog.Anchor );\n  }\n}( this, function factory( Anchor ) {\n\nvar Group = Anchor.subclass({\n  updateSort: false,\n  visible: true,\n});\n\n// ----- update ----- //\n\nGroup.prototype.updateSortValue = function() {\n  var sortValueTotal = 0;\n  this.flatGraph.forEach( function( item ) {\n    item.updateSortValue();\n    sortValueTotal += item.sortValue;\n  } );\n  // average sort value of all points\n  // def not geometrically correct, but works for me\n  this.sortValue = sortValueTotal / this.flatGraph.length;\n\n  if ( this.updateSort ) {\n    this.flatGraph.sort( Anchor.shapeSorter );\n  }\n};\n\n// ----- render ----- //\n\nGroup.prototype.render = function( ctx, renderer ) {\n  if ( !this.visible ) {\n    return;\n  }\n\n  this.flatGraph.forEach( function( item ) {\n    item.render( ctx, renderer );\n  } );\n};\n\n// actual group flatGraph only used inside group\nGroup.prototype.updateFlatGraph = function() {\n  // do not include self\n  var flatGraph = [];\n  this.flatGraph = this.addChildFlatGraph( flatGraph );\n};\n\n// do not include children, group handles rendering & sorting internally\nGroup.prototype.getFlatGraph = function() {\n  return [ this ];\n};\n\nreturn Group;\n\n} ) );\n"
  },
  {
    "path": "js/hemisphere.js",
    "content": "/**\n * Hemisphere composite shape\n */\n\n( function( root, factory ) {\n  // module definition\n  if ( typeof module == 'object' && module.exports ) {\n    // CommonJS\n    module.exports = factory( require('./boilerplate'), require('./vector'),\n        require('./anchor'), require('./ellipse') );\n  } else {\n    // browser global\n    var Zdog = root.Zdog;\n    Zdog.Hemisphere = factory( Zdog, Zdog.Vector, Zdog.Anchor, Zdog.Ellipse );\n  }\n}( this, function factory( utils, Vector, Anchor, Ellipse ) {\n\nvar Hemisphere = Ellipse.subclass({\n  fill: true,\n});\n\nvar TAU = utils.TAU;\n\nHemisphere.prototype.create = function( /* options */) {\n  // call super\n  Ellipse.prototype.create.apply( this, arguments );\n  // composite shape, create child shapes\n  this.apex = new Anchor({\n    addTo: this,\n    translate: { z: this.diameter / 2 },\n  });\n  // vector used for calculation\n  this.renderCentroid = new Vector();\n};\n\nHemisphere.prototype.updateSortValue = function() {\n  // centroid of hemisphere is 3/8 between origin and apex\n  this.renderCentroid.set( this.renderOrigin )\n    .lerp( this.apex.renderOrigin, 3/8 );\n  this.sortValue = this.renderCentroid.z;\n};\n\nHemisphere.prototype.render = function( ctx, renderer ) {\n  this.renderDome( ctx, renderer );\n  // call super\n  Ellipse.prototype.render.apply( this, arguments );\n};\n\nHemisphere.prototype.renderDome = function( ctx, renderer ) {\n  if ( !this.visible ) {\n    return;\n  }\n  var elem = this.getDomeRenderElement( ctx, renderer );\n  var contourAngle = Math.atan2( this.renderNormal.y, this.renderNormal.x );\n  var domeRadius = this.diameter / 2 * this.renderNormal.magnitude();\n  var x = this.renderOrigin.x;\n  var y = this.renderOrigin.y;\n\n  if ( renderer.isCanvas ) {\n    // canvas\n    var startAngle = contourAngle + TAU/4;\n    var endAngle = contourAngle - TAU/4;\n    ctx.beginPath();\n    ctx.arc( x, y, domeRadius, startAngle, endAngle );\n  } else if ( renderer.isSvg ) {\n    // svg\n    contourAngle = ( contourAngle - TAU/4 ) / TAU * 360;\n    this.domeSvgElement.setAttribute( 'd', 'M ' + -domeRadius + ',0 A ' +\n        domeRadius + ',' + domeRadius + ' 0 0 1 ' + domeRadius + ',0' );\n    this.domeSvgElement.setAttribute( 'transform',\n        'translate(' + x + ',' + y + ' ) rotate(' + contourAngle + ')' );\n  }\n\n  renderer.stroke( ctx, elem, this.stroke, this.color, this.getLineWidth() );\n  renderer.fill( ctx, elem, this.fill, this.color );\n  renderer.end( ctx, elem );\n};\n\nvar svgURI = 'http://www.w3.org/2000/svg';\n\nHemisphere.prototype.getDomeRenderElement = function( ctx, renderer ) {\n  if ( !renderer.isSvg ) {\n    return;\n  }\n  if ( !this.domeSvgElement ) {\n    // create svgElement\n    this.domeSvgElement = document.createElementNS( svgURI, 'path' );\n    this.domeSvgElement.setAttribute( 'stroke-linecap', 'round' );\n    this.domeSvgElement.setAttribute( 'stroke-linejoin', 'round' );\n  }\n  return this.domeSvgElement;\n};\n\nreturn Hemisphere;\n\n} ) );\n"
  },
  {
    "path": "js/illustration.js",
    "content": "/**\n * Illustration\n */\n\n( function( root, factory ) {\n  // module definition\n  if ( typeof module == 'object' && module.exports ) {\n    // CommonJS\n    module.exports = factory( require('./boilerplate'), require('./anchor'),\n        require('./dragger') );\n  } else {\n    // browser global\n    var Zdog = root.Zdog;\n    Zdog.Illustration = factory( Zdog, Zdog.Anchor, Zdog.Dragger );\n  }\n}( this, function factory( utils, Anchor, Dragger ) {\n\nfunction noop() {}\nvar TAU = utils.TAU;\n\nvar Illustration = Anchor.subclass({\n  element: undefined,\n  centered: true,\n  zoom: 1,\n  dragRotate: false,\n  resize: false,\n  onPrerender: noop,\n  onDragStart: noop,\n  onDragMove: noop,\n  onDragEnd: noop,\n  onResize: noop,\n});\n\nutils.extend( Illustration.prototype, Dragger.prototype );\n\nIllustration.prototype.create = function( options ) {\n  Anchor.prototype.create.call( this, options );\n  Dragger.prototype.create.call( this, options );\n  this.setElement( this.element );\n  this.setDragRotate( this.dragRotate );\n  this.setResize( this.resize );\n};\n\nIllustration.prototype.setElement = function( element ) {\n  element = this.getQueryElement( element );\n  if ( !element ) {\n    throw new Error( 'Zdog.Illustration element required. Set to ' + element );\n  }\n\n  var nodeName = element.nodeName.toLowerCase();\n  if ( nodeName == 'canvas' ) {\n    this.setCanvas( element );\n  } else if ( nodeName == 'svg' ) {\n    this.setSvg( element );\n  }\n};\n\nIllustration.prototype.setSize = function( width, height ) {\n  width = Math.round( width );\n  height = Math.round( height );\n  if ( this.isCanvas ) {\n    this.setSizeCanvas( width, height );\n  } else if ( this.isSvg ) {\n    this.setSizeSvg( width, height );\n  }\n};\n\nIllustration.prototype.setResize = function( resize ) {\n  this.resize = resize;\n  // create resize event listener\n  if ( !this.resizeListener ) {\n    this.resizeListener = this.onWindowResize.bind( this );\n  }\n  // add/remove event listener\n  if ( resize ) {\n    window.addEventListener( 'resize', this.resizeListener );\n    this.onWindowResize();\n  } else {\n    window.removeEventListener( 'resize', this.resizeListener );\n  }\n};\n\n// TODO debounce this?\nIllustration.prototype.onWindowResize = function() {\n  this.setMeasuredSize();\n  this.onResize( this.width, this.height );\n};\n\nIllustration.prototype.setMeasuredSize = function() {\n  var width, height;\n  var isFullscreen = this.resize == 'fullscreen';\n  if ( isFullscreen ) {\n    width = window.innerWidth;\n    height = window.innerHeight;\n  } else {\n    var rect = this.element.getBoundingClientRect();\n    width = rect.width;\n    height = rect.height;\n  }\n  this.setSize( width, height );\n};\n\n// ----- render ----- //\n\nIllustration.prototype.renderGraph = function( item ) {\n  if ( this.isCanvas ) {\n    this.renderGraphCanvas( item );\n  } else if ( this.isSvg ) {\n    this.renderGraphSvg( item );\n  }\n};\n\n// combo method\nIllustration.prototype.updateRenderGraph = function( item ) {\n  this.updateGraph();\n  this.renderGraph( item );\n};\n\n// ----- canvas ----- //\n\nIllustration.prototype.setCanvas = function( element ) {\n  this.element = element;\n  this.isCanvas = true;\n  // update related properties\n  this.ctx = this.element.getContext('2d');\n  // set initial size\n  this.setSizeCanvas( element.width, element.height );\n};\n\nIllustration.prototype.setSizeCanvas = function( width, height ) {\n  this.width = width;\n  this.height = height;\n  // up-rez for hi-DPI devices\n  var pixelRatio = this.pixelRatio = window.devicePixelRatio || 1;\n  this.element.width = this.canvasWidth = width * pixelRatio;\n  this.element.height = this.canvasHeight = height * pixelRatio;\n  var needsHighPixelRatioSizing = pixelRatio > 1 && !this.resize;\n  if ( needsHighPixelRatioSizing ) {\n    this.element.style.width = width + 'px';\n    this.element.style.height = height + 'px';\n  }\n};\n\nIllustration.prototype.renderGraphCanvas = function( item ) {\n  item = item || this;\n  this.prerenderCanvas();\n  Anchor.prototype.renderGraphCanvas.call( item, this.ctx );\n  this.postrenderCanvas();\n};\n\nIllustration.prototype.prerenderCanvas = function() {\n  var ctx = this.ctx;\n  ctx.lineCap = 'round';\n  ctx.lineJoin = 'round';\n  ctx.clearRect( 0, 0, this.canvasWidth, this.canvasHeight );\n  ctx.save();\n  if ( this.centered ) {\n    var centerX = this.width / 2 * this.pixelRatio;\n    var centerY = this.height / 2 * this.pixelRatio;\n    ctx.translate( centerX, centerY );\n  }\n  var scale = this.pixelRatio * this.zoom;\n  ctx.scale( scale, scale );\n  this.onPrerender( ctx );\n};\n\nIllustration.prototype.postrenderCanvas = function() {\n  this.ctx.restore();\n};\n\n// ----- svg ----- //\n\nIllustration.prototype.setSvg = function( element ) {\n  this.element = element;\n  this.isSvg = true;\n  this.pixelRatio = 1;\n  // set initial size from width & height attributes\n  var width = element.getAttribute('width');\n  var height = element.getAttribute('height');\n  this.setSizeSvg( width, height );\n};\n\nIllustration.prototype.setSizeSvg = function( width, height ) {\n  this.width = width;\n  this.height = height;\n  var viewWidth = width / this.zoom;\n  var viewHeight = height / this.zoom;\n  var viewX = this.centered ? -viewWidth/2 : 0;\n  var viewY = this.centered ? -viewHeight/2 : 0;\n  this.element.setAttribute( 'viewBox', viewX + ' ' + viewY + ' ' +\n    viewWidth + ' ' + viewHeight );\n  if ( this.resize ) {\n    // remove size attributes, let size be determined by viewbox\n    this.element.removeAttribute('width');\n    this.element.removeAttribute('height');\n  } else {\n    this.element.setAttribute( 'width', width );\n    this.element.setAttribute( 'height', height );\n  }\n};\n\nIllustration.prototype.renderGraphSvg = function( item ) {\n  item = item || this;\n  empty( this.element );\n  this.onPrerender( this.element );\n  Anchor.prototype.renderGraphSvg.call( item, this.element );\n};\n\nfunction empty( element ) {\n  while ( element.firstChild ) {\n    element.removeChild( element.firstChild );\n  }\n}\n\n// ----- drag ----- //\n\nIllustration.prototype.setDragRotate = function( item ) {\n  if ( !item ) {\n    return;\n  } else if ( item === true ) {\n    /* eslint consistent-this: \"off\" */\n    item = this;\n  }\n  this.dragRotate = item;\n\n  this.bindDrag( this.element );\n};\n\nIllustration.prototype.dragStart = function( /* event, pointer */) {\n  this.dragStartRX = this.dragRotate.rotate.x;\n  this.dragStartRY = this.dragRotate.rotate.y;\n  Dragger.prototype.dragStart.apply( this, arguments );\n};\n\nIllustration.prototype.dragMove = function( event, pointer ) {\n  var moveX = pointer.pageX - this.dragStartX;\n  var moveY = pointer.pageY - this.dragStartY;\n  var displaySize = Math.min( this.width, this.height );\n  var moveRY = moveX/displaySize * TAU;\n  var moveRX = moveY/displaySize * TAU;\n  this.dragRotate.rotate.x = this.dragStartRX - moveRX;\n  this.dragRotate.rotate.y = this.dragStartRY - moveRY;\n  Dragger.prototype.dragMove.apply( this, arguments );\n};\n\nreturn Illustration;\n\n} ) );\n"
  },
  {
    "path": "js/index.js",
    "content": "/**\n * Index\n */\n\n( function( root, factory ) {\n  // module definition\n  if ( typeof module == 'object' && module.exports ) {\n    // CommonJS\n    module.exports = factory(\n        require('./boilerplate'),\n        require('./canvas-renderer'),\n        require('./svg-renderer'),\n        require('./vector'),\n        require('./anchor'),\n        require('./dragger'),\n        require('./illustration'),\n        require('./path-command'),\n        require('./shape'),\n        require('./group'),\n        require('./rect'),\n        require('./rounded-rect'),\n        require('./ellipse'),\n        require('./polygon'),\n        require('./hemisphere'),\n        require('./cylinder'),\n        require('./cone'),\n        require('./box')\n    );\n  } else if ( typeof define == 'function' && define.amd ) {\n    /* globals define */ // AMD\n    define( 'zdog', [], root.Zdog );\n  }\n/* eslint-disable max-params */\n} )( this, function factory( Zdog, CanvasRenderer, SvgRenderer, Vector, Anchor,\n    Dragger, Illustration, PathCommand, Shape, Group, Rect, RoundedRect,\n    Ellipse, Polygon, Hemisphere, Cylinder, Cone, Box ) {\n/* eslint-enable max-params */\n\n      Zdog.CanvasRenderer = CanvasRenderer;\n      Zdog.SvgRenderer = SvgRenderer;\n      Zdog.Vector = Vector;\n      Zdog.Anchor = Anchor;\n      Zdog.Dragger = Dragger;\n      Zdog.Illustration = Illustration;\n      Zdog.PathCommand = PathCommand;\n      Zdog.Shape = Shape;\n      Zdog.Group = Group;\n      Zdog.Rect = Rect;\n      Zdog.RoundedRect = RoundedRect;\n      Zdog.Ellipse = Ellipse;\n      Zdog.Polygon = Polygon;\n      Zdog.Hemisphere = Hemisphere;\n      Zdog.Cylinder = Cylinder;\n      Zdog.Cone = Cone;\n      Zdog.Box = Box;\n\n      return Zdog;\n} );\n"
  },
  {
    "path": "js/path-command.js",
    "content": "/**\n * PathCommand\n */\n\n( function( root, factory ) {\n  // module definition\n  if ( typeof module == 'object' && module.exports ) {\n    // CommonJS\n    module.exports = factory( require('./vector') );\n  } else {\n    // browser global\n    var Zdog = root.Zdog;\n    Zdog.PathCommand = factory( Zdog.Vector );\n  }\n}( this, function factory( Vector ) {\n\nfunction PathCommand( method, points, previousPoint ) {\n  this.method = method;\n  this.points = points.map( mapVectorPoint );\n  this.renderPoints = points.map( mapNewVector );\n  this.previousPoint = previousPoint;\n  this.endRenderPoint = this.renderPoints[ this.renderPoints.length - 1 ];\n  // arc actions come with previous point & corner point\n  // but require bezier control points\n  if ( method == 'arc' ) {\n    this.controlPoints = [ new Vector(), new Vector() ];\n  }\n}\n\nfunction mapVectorPoint( point ) {\n  if ( point instanceof Vector ) {\n    return point;\n  } else {\n    return new Vector( point );\n  }\n}\n\nfunction mapNewVector( point ) {\n  return new Vector( point );\n}\n\nPathCommand.prototype.reset = function() {\n  // reset renderPoints back to orignal points position\n  var points = this.points;\n  this.renderPoints.forEach( function( renderPoint, i ) {\n    var point = points[i];\n    renderPoint.set( point );\n  } );\n};\n\nPathCommand.prototype.transform = function( translation, rotation, scale ) {\n  this.renderPoints.forEach( function( renderPoint ) {\n    renderPoint.transform( translation, rotation, scale );\n  } );\n};\n\nPathCommand.prototype.render = function( ctx, elem, renderer ) {\n  return this[ this.method ]( ctx, elem, renderer );\n};\n\nPathCommand.prototype.move = function( ctx, elem, renderer ) {\n  return renderer.move( ctx, elem, this.renderPoints[0] );\n};\n\nPathCommand.prototype.line = function( ctx, elem, renderer ) {\n  return renderer.line( ctx, elem, this.renderPoints[0] );\n};\n\nPathCommand.prototype.bezier = function( ctx, elem, renderer ) {\n  var cp0 = this.renderPoints[0];\n  var cp1 = this.renderPoints[1];\n  var end = this.renderPoints[2];\n  return renderer.bezier( ctx, elem, cp0, cp1, end );\n};\n\nvar arcHandleLength = 9/16;\n\nPathCommand.prototype.arc = function( ctx, elem, renderer ) {\n  var prev = this.previousPoint;\n  var corner = this.renderPoints[0];\n  var end = this.renderPoints[1];\n  var cp0 = this.controlPoints[0];\n  var cp1 = this.controlPoints[1];\n  cp0.set( prev ).lerp( corner, arcHandleLength );\n  cp1.set( end ).lerp( corner, arcHandleLength );\n  return renderer.bezier( ctx, elem, cp0, cp1, end );\n};\n\nreturn PathCommand;\n\n} ) );\n"
  },
  {
    "path": "js/polygon.js",
    "content": "/**\n * Shape\n */\n\n( function( root, factory ) {\n  // module definition\n  if ( typeof module == 'object' && module.exports ) {\n    // CommonJS\n    module.exports = factory( require('./boilerplate'), require('./shape') );\n  } else {\n    // browser global\n    var Zdog = root.Zdog;\n    Zdog.Polygon = factory( Zdog, Zdog.Shape );\n  }\n}( this, function factory( utils, Shape ) {\n\nvar Polygon = Shape.subclass({\n  sides: 3,\n  radius: 0.5,\n});\n\nvar TAU = utils.TAU;\n\nPolygon.prototype.setPath = function() {\n  this.path = [];\n  for ( var i = 0; i < this.sides; i++ ) {\n    var theta = i / this.sides * TAU - TAU/4;\n    var x = Math.cos( theta ) * this.radius;\n    var y = Math.sin( theta ) * this.radius;\n    this.path.push({ x: x, y: y });\n  }\n};\n\nreturn Polygon;\n\n} ) );\n"
  },
  {
    "path": "js/rect.js",
    "content": "/**\n * Rect\n */\n\n( function( root, factory ) {\n  // module definition\n  if ( typeof module == 'object' && module.exports ) {\n    // CommonJS\n    module.exports = factory( require('./shape') );\n  } else {\n    // browser global\n    var Zdog = root.Zdog;\n    Zdog.Rect = factory( Zdog.Shape );\n  }\n}( this, function factory( Shape ) {\n\nvar Rect = Shape.subclass({\n  width: 1,\n  height: 1,\n});\n\nRect.prototype.setPath = function() {\n  var x = this.width / 2;\n  var y = this.height / 2;\n  /* eslint key-spacing: \"off\" */\n  this.path = [\n    { x: -x, y: -y },\n    { x:  x, y: -y },\n    { x:  x, y:  y },\n    { x: -x, y:  y },\n  ];\n};\n\nreturn Rect;\n\n} ) );\n"
  },
  {
    "path": "js/rounded-rect.js",
    "content": "/**\n * RoundedRect\n */\n\n( function( root, factory ) {\n  // module definition\n  if ( typeof module == 'object' && module.exports ) {\n    // CommonJS\n    module.exports = factory( require('./shape') );\n  } else {\n    // browser global\n    var Zdog = root.Zdog;\n    Zdog.RoundedRect = factory( Zdog.Shape );\n  }\n}( this, function factory( Shape ) {\n\nvar RoundedRect = Shape.subclass({\n  width: 1,\n  height: 1,\n  cornerRadius: 0.25,\n  closed: false,\n});\n\nRoundedRect.prototype.setPath = function() {\n  /* eslint\n     id-length: [ \"error\", { \"min\": 2, \"exceptions\": [ \"x\", \"y\" ] }],\n     key-spacing: \"off\" */\n  var xA = this.width / 2;\n  var yA = this.height / 2;\n  var shortSide = Math.min( xA, yA );\n  var cornerRadius = Math.min( this.cornerRadius, shortSide );\n  var xB = xA - cornerRadius;\n  var yB = yA - cornerRadius;\n  var path = [\n    // top right corner\n    { x: xB, y: -yA },\n    { arc: [\n      { x: xA, y: -yA },\n      { x: xA, y: -yB },\n    ] },\n  ];\n  // bottom right corner\n  if ( yB ) {\n    path.push({ x: xA, y: yB });\n  }\n  path.push({ arc: [\n    { x: xA, y:  yA },\n    { x: xB, y:  yA },\n  ] });\n  // bottom left corner\n  if ( xB ) {\n    path.push({ x: -xB, y: yA });\n  }\n  path.push({ arc: [\n    { x: -xA, y:  yA },\n    { x: -xA, y:  yB },\n  ] });\n  // top left corner\n  if ( yB ) {\n    path.push({ x: -xA, y: -yB });\n  }\n  path.push({ arc: [\n    { x: -xA, y: -yA },\n    { x: -xB, y: -yA },\n  ] });\n\n  // back to top right corner\n  if ( xB ) {\n    path.push({ x: xB, y: -yA });\n  }\n\n  this.path = path;\n};\n\nreturn RoundedRect;\n\n} ) );\n"
  },
  {
    "path": "js/shape.js",
    "content": "/**\n * Shape\n */\n\n( function( root, factory ) {\n  // module definition\n  if ( typeof module == 'object' && module.exports ) {\n    // CommonJS\n    module.exports = factory( require('./boilerplate'), require('./vector'),\n        require('./path-command'), require('./anchor') );\n  } else {\n    // browser global\n    var Zdog = root.Zdog;\n    Zdog.Shape = factory( Zdog, Zdog.Vector, Zdog.PathCommand, Zdog.Anchor );\n  }\n}( this, function factory( utils, Vector, PathCommand, Anchor ) {\n\nvar Shape = Anchor.subclass({\n  stroke: 1,\n  fill: false,\n  color: '#333',\n  closed: true,\n  visible: true,\n  path: [ {} ],\n  front: { z: 1 },\n  backface: true,\n});\n\nShape.prototype.create = function( options ) {\n  Anchor.prototype.create.call( this, options );\n  this.updatePath();\n  // front\n  this.front = new Vector( options.front || this.front );\n  this.renderFront = new Vector( this.front );\n  this.renderNormal = new Vector();\n};\n\nvar actionNames = [\n  'move',\n  'line',\n  'bezier',\n  'arc',\n];\n\nShape.prototype.updatePath = function() {\n  this.setPath();\n  this.updatePathCommands();\n};\n\n// place holder for Ellipse, Rect, etc.\nShape.prototype.setPath = function() {};\n\n// parse path into PathCommands\nShape.prototype.updatePathCommands = function() {\n  var previousPoint;\n  this.pathCommands = this.path.map( function( pathPart, i ) {\n    // pathPart can be just vector coordinates -> { x, y, z }\n    // or path instruction -> { arc: [ {x0,y0,z0}, {x1,y1,z1} ] }\n    var keys = Object.keys( pathPart );\n    var method = keys[0];\n    var points = pathPart[ method ];\n    // default to line if no instruction\n    var isInstruction = keys.length == 1 && actionNames.indexOf( method ) != -1;\n    if ( !isInstruction ) {\n      method = 'line';\n      points = pathPart;\n    }\n    // munge single-point methods like line & move without arrays\n    var isLineOrMove = method == 'line' || method == 'move';\n    var isPointsArray = Array.isArray( points );\n    if ( isLineOrMove && !isPointsArray ) {\n      points = [ points ];\n    }\n\n    // first action is always move\n    method = i === 0 ? 'move' : method;\n    // arcs require previous last point\n    var command = new PathCommand( method, points, previousPoint );\n    // update previousLastPoint\n    previousPoint = command.endRenderPoint;\n    return command;\n  } );\n};\n\n// ----- update ----- //\n\nShape.prototype.reset = function() {\n  this.renderOrigin.set( this.origin );\n  this.renderFront.set( this.front );\n  // reset command render points\n  this.pathCommands.forEach( function( command ) {\n    command.reset();\n  } );\n};\n\nShape.prototype.transform = function( translation, rotation, scale ) {\n  // calculate render points backface visibility & cone/hemisphere shapes\n  this.renderOrigin.transform( translation, rotation, scale );\n  this.renderFront.transform( translation, rotation, scale );\n  this.renderNormal.set( this.renderOrigin ).subtract( this.renderFront );\n  // transform points\n  this.pathCommands.forEach( function( command ) {\n    command.transform( translation, rotation, scale );\n  } );\n  // transform children\n  this.children.forEach( function( child ) {\n    child.transform( translation, rotation, scale );\n  } );\n};\n\nShape.prototype.updateSortValue = function() {\n  // sort by average z of all points\n  // def not geometrically correct, but works for me\n  var pointCount = this.pathCommands.length;\n  var firstPoint = this.pathCommands[0].endRenderPoint;\n  var lastPoint = this.pathCommands[ pointCount - 1 ].endRenderPoint;\n  // ignore the final point if self closing shape\n  var isSelfClosing = pointCount > 2 && firstPoint.isSame( lastPoint );\n  if ( isSelfClosing ) {\n    pointCount -= 1;\n  }\n\n  var sortValueTotal = 0;\n  for ( var i = 0; i < pointCount; i++ ) {\n    sortValueTotal += this.pathCommands[i].endRenderPoint.z;\n  }\n  this.sortValue = sortValueTotal/pointCount;\n};\n\n// ----- render ----- //\n\nShape.prototype.render = function( ctx, renderer ) {\n  var length = this.pathCommands.length;\n  if ( !this.visible || !length ) {\n    return;\n  }\n  // do not render if hiding backface\n  this.isFacingBack = this.renderNormal.z > 0;\n  if ( !this.backface && this.isFacingBack ) {\n    return;\n  }\n  if ( !renderer ) {\n    throw new Error( 'Zdog renderer required. Set to ' + renderer );\n  }\n  // render dot or path\n  var isDot = length == 1;\n  if ( renderer.isCanvas && isDot ) {\n    this.renderCanvasDot( ctx, renderer );\n  } else {\n    this.renderPath( ctx, renderer );\n  }\n};\n\nvar TAU = utils.TAU;\n// Safari does not render lines with no size, have to render circle instead\nShape.prototype.renderCanvasDot = function( ctx ) {\n  var lineWidth = this.getLineWidth();\n  if ( !lineWidth ) {\n    return;\n  }\n  ctx.fillStyle = this.getRenderColor();\n  var point = this.pathCommands[0].endRenderPoint;\n  ctx.beginPath();\n  var radius = lineWidth/2;\n  ctx.arc( point.x, point.y, radius, 0, TAU );\n  ctx.fill();\n};\n\nShape.prototype.getLineWidth = function() {\n  if ( !this.stroke ) {\n    return 0;\n  }\n  if ( this.stroke == true ) {\n    return 1;\n  }\n  return this.stroke;\n};\n\nShape.prototype.getRenderColor = function() {\n  // use backface color if applicable\n  var isBackfaceColor = typeof this.backface == 'string' && this.isFacingBack;\n  var color = isBackfaceColor ? this.backface : this.color;\n  return color;\n};\n\nShape.prototype.renderPath = function( ctx, renderer ) {\n  var elem = this.getRenderElement( ctx, renderer );\n  var isTwoPoints = this.pathCommands.length == 2 &&\n    this.pathCommands[1].method == 'line';\n  var isClosed = !isTwoPoints && this.closed;\n  var color = this.getRenderColor();\n\n  renderer.renderPath( ctx, elem, this.pathCommands, isClosed );\n  renderer.stroke( ctx, elem, this.stroke, color, this.getLineWidth() );\n  renderer.fill( ctx, elem, this.fill, color );\n  renderer.end( ctx, elem );\n};\n\nvar svgURI = 'http://www.w3.org/2000/svg';\n\nShape.prototype.getRenderElement = function( ctx, renderer ) {\n  if ( !renderer.isSvg ) {\n    return;\n  }\n  if ( !this.svgElement ) {\n    // create svgElement\n    this.svgElement = document.createElementNS( svgURI, 'path' );\n    this.svgElement.setAttribute( 'stroke-linecap', 'round' );\n    this.svgElement.setAttribute( 'stroke-linejoin', 'round' );\n  }\n  return this.svgElement;\n};\n\nreturn Shape;\n\n} ) );\n"
  },
  {
    "path": "js/svg-renderer.js",
    "content": "/**\n * SvgRenderer\n */\n\n( function( root, factory ) {\n  // module definition\n  if ( typeof module == 'object' && module.exports ) {\n    // CommonJS\n    module.exports = factory();\n  } else {\n    // browser global\n    root.Zdog.SvgRenderer = factory();\n  }\n}( this, function factory() {\n\nvar SvgRenderer = { isSvg: true };\n\n// round path coordinates to 3 decimals\nvar round = SvgRenderer.round = function( num ) {\n  return Math.round( num * 1000 ) / 1000;\n};\n\nfunction getPointString( point ) {\n  return round( point.x ) + ',' + round( point.y ) + ' ';\n}\n\nSvgRenderer.begin = function() {};\n\nSvgRenderer.move = function( svg, elem, point ) {\n  return 'M' + getPointString( point );\n};\n\nSvgRenderer.line = function( svg, elem, point ) {\n  return 'L' + getPointString( point );\n};\n\nSvgRenderer.bezier = function( svg, elem, cp0, cp1, end ) {\n  return 'C' + getPointString( cp0 ) + getPointString( cp1 ) +\n    getPointString( end );\n};\n\nSvgRenderer.closePath = function( /* elem */) {\n  return 'Z';\n};\n\nSvgRenderer.setPath = function( svg, elem, pathValue ) {\n  elem.setAttribute( 'd', pathValue );\n};\n\nSvgRenderer.renderPath = function( svg, elem, pathCommands, isClosed ) {\n  var pathValue = '';\n  pathCommands.forEach( function( command ) {\n    pathValue += command.render( svg, elem, SvgRenderer );\n  } );\n  if ( isClosed ) {\n    pathValue += this.closePath( svg, elem );\n  }\n  this.setPath( svg, elem, pathValue );\n};\n\nSvgRenderer.stroke = function( svg, elem, isStroke, color, lineWidth ) {\n  if ( !isStroke ) {\n    return;\n  }\n  elem.setAttribute( 'stroke', color );\n  elem.setAttribute( 'stroke-width', lineWidth );\n};\n\nSvgRenderer.fill = function( svg, elem, isFill, color ) {\n  var fillColor = isFill ? color : 'none';\n  elem.setAttribute( 'fill', fillColor );\n};\n\nSvgRenderer.end = function( svg, elem ) {\n  svg.appendChild( elem );\n};\n\nreturn SvgRenderer;\n\n} ) );\n"
  },
  {
    "path": "js/vector.js",
    "content": "/**\n * Vector\n */\n\n( function( root, factory ) {\n  // module definition\n  if ( typeof module == 'object' && module.exports ) {\n    // CommonJS\n    module.exports = factory( require('./boilerplate') );\n  } else {\n    // browser global\n    var Zdog = root.Zdog;\n    Zdog.Vector = factory( Zdog );\n  }\n\n}( this, function factory( utils ) {\n\nfunction Vector( position ) {\n  this.set( position );\n}\n\nvar TAU = utils.TAU;\n\n// 'pos' = 'position'\nVector.prototype.set = function( pos ) {\n  this.x = pos && pos.x || 0;\n  this.y = pos && pos.y || 0;\n  this.z = pos && pos.z || 0;\n  return this;\n};\n\n// set coordinates without sanitizing\n// vec.write({ y: 2 }) only sets y coord\nVector.prototype.write = function( pos ) {\n  if ( !pos ) {\n    return this;\n  }\n  this.x = pos.x != undefined ? pos.x : this.x;\n  this.y = pos.y != undefined ? pos.y : this.y;\n  this.z = pos.z != undefined ? pos.z : this.z;\n  return this;\n};\n\nVector.prototype.rotate = function( rotation ) {\n  if ( !rotation ) {\n    return;\n  }\n  this.rotateZ( rotation.z );\n  this.rotateY( rotation.y );\n  this.rotateX( rotation.x );\n  return this;\n};\n\nVector.prototype.rotateZ = function( angle ) {\n  rotateProperty( this, angle, 'x', 'y' );\n};\n\nVector.prototype.rotateX = function( angle ) {\n  rotateProperty( this, angle, 'y', 'z' );\n};\n\nVector.prototype.rotateY = function( angle ) {\n  rotateProperty( this, angle, 'x', 'z' );\n};\n\nfunction rotateProperty( vec, angle, propA, propB ) {\n  if ( !angle || angle % TAU === 0 ) {\n    return;\n  }\n  var cos = Math.cos( angle );\n  var sin = Math.sin( angle );\n  var a = vec[ propA ];\n  var b = vec[ propB ];\n  vec[ propA ] = a * cos - b * sin;\n  vec[ propB ] = b * cos + a * sin;\n}\n\nVector.prototype.isSame = function( pos ) {\n  if ( !pos ) {\n    return false;\n  }\n  return this.x === pos.x && this.y === pos.y && this.z === pos.z;\n};\n\nVector.prototype.add = function( pos ) {\n  if ( !pos ) {\n    return this;\n  }\n  this.x += pos.x || 0;\n  this.y += pos.y || 0;\n  this.z += pos.z || 0;\n  return this;\n};\n\nVector.prototype.subtract = function( pos ) {\n  if ( !pos ) {\n    return this;\n  }\n  this.x -= pos.x || 0;\n  this.y -= pos.y || 0;\n  this.z -= pos.z || 0;\n  return this;\n};\n\nVector.prototype.multiply = function( pos ) {\n  if ( pos == undefined ) {\n    return this;\n  }\n  // multiple all values by same number\n  if ( typeof pos == 'number' ) {\n    this.x *= pos;\n    this.y *= pos;\n    this.z *= pos;\n  } else {\n    // multiply object\n    this.x *= pos.x != undefined ? pos.x : 1;\n    this.y *= pos.y != undefined ? pos.y : 1;\n    this.z *= pos.z != undefined ? pos.z : 1;\n  }\n  return this;\n};\n\nVector.prototype.transform = function( translation, rotation, scale ) {\n  this.multiply( scale );\n  this.rotate( rotation );\n  this.add( translation );\n  return this;\n};\n\nVector.prototype.lerp = function( pos, alpha ) {\n  this.x = utils.lerp( this.x, pos.x || 0, alpha );\n  this.y = utils.lerp( this.y, pos.y || 0, alpha );\n  this.z = utils.lerp( this.z, pos.z || 0, alpha );\n  return this;\n};\n\nVector.prototype.magnitude = function() {\n  var sum = this.x * this.x + this.y * this.y + this.z * this.z;\n  return getMagnitudeSqrt( sum );\n};\n\nfunction getMagnitudeSqrt( sum ) {\n  // PERF: check if sum ~= 1 and skip sqrt\n  if ( Math.abs( sum - 1 ) < 0.00000001 ) {\n    return 1;\n  }\n  return Math.sqrt( sum );\n}\n\nVector.prototype.magnitude2d = function() {\n  var sum = this.x * this.x + this.y * this.y;\n  return getMagnitudeSqrt( sum );\n};\n\nVector.prototype.copy = function() {\n  return new Vector( this );\n};\n\nreturn Vector;\n\n} ) );\n"
  },
  {
    "path": "package.json",
    "content": "{\n  \"name\": \"zdog\",\n  \"version\": \"1.1.3\",\n  \"description\": \"Round, flat, designer-friendly pseudo-3D engine\",\n  \"main\": \"js/index.js\",\n  \"unpkg\": \"dist/zdog.dist.min.js\",\n  \"files\": [\n    \"dist/*.*\",\n    \"js/*.*\",\n    \"!js/.*\",\n    \"!dist/.*\"\n  ],\n  \"devDependencies\": {\n    \"eslint\": \"^8.7.0\",\n    \"eslint-plugin-metafizzy\": \"^1.0.0\",\n    \"uglify-js\": \"^3.6.3\"\n  },\n  \"scripts\": {\n    \"bundle\": \"node tasks/bundle\",\n    \"dist\": \"npm run bundle && npm run uglify\",\n    \"lint\": \"npx eslint .\",\n    \"lintFix\": \"npx eslint . --fix\",\n    \"preversion\": \"npm run lint\",\n    \"version\": \"node tasks/version && npm run dist && git add -A dist js\",\n    \"test\": \"npm run lint\",\n    \"uglify\": \"npx uglifyjs dist/zdog.dist.js -o dist/zdog.dist.min.js --mangle --comments /^!/\"\n  },\n  \"repository\": {\n    \"type\": \"git\",\n    \"url\": \"git+https://github.com/metafizzy/zdog.git\"\n  },\n  \"keywords\": [\n    \"3D\",\n    \"canvas\",\n    \"svg\"\n  ],\n  \"author\": \"David DeSandro\",\n  \"license\": \"MIT\",\n  \"bugs\": {\n    \"url\": \"https://github.com/metafizzy/zdog/issues\"\n  },\n  \"homepage\": \"https://zzz.dog\"\n}\n"
  },
  {
    "path": "tasks/.eslintrc.js",
    "content": "/* eslint-env node */\n\nmodule.exports = {\n  plugins: [ 'metafizzy' ],\n  extends: 'plugin:metafizzy/node',\n  env: {\n    browser: false,\n    commonjs: true,\n  },\n  rules: {\n  },\n};\n"
  },
  {
    "path": "tasks/bundle.js",
    "content": "const fs = require('fs');\nconst execSync = require('child_process').execSync;\n\n// get file paths from index.js\nconst indexPath = 'js/index.js';\nlet indexSrc = fs.readFileSync( `./${indexPath}`, 'utf8' );\nlet cjsBlockRegex = /module\\.exports = factory\\([\\w ,'.\\-()/\\n]+;/i;\nlet cjsBlockMatch = indexSrc.match( cjsBlockRegex );\nlet paths = cjsBlockMatch[0].match( /require\\('([.\\-/\\w]+)'\\)/gi );\n\npaths = paths.map( function( path ) {\n  return path.replace( \"require('.\", 'js' ).replace( \"')\", '.js' );\n} );\npaths.push( indexPath );\n\nexecSync(`cat ${paths.join(' ')} > dist/zdog.dist.js`);\n\nconsole.log('bundled dist/zdog.dist.js');\n"
  },
  {
    "path": "tasks/version.js",
    "content": "const fs = require('fs');\nconst version = require('../package.json').version;\n\nconst boilerplatePath = 'js/boilerplate.js';\n\nlet boilerplateSrc = fs.readFileSync( boilerplatePath, 'utf8' );\n\nboilerplateSrc = boilerplateSrc.replace( /\\n \\* Zdog v\\d+\\.\\d+\\.\\d+/,\n    `\\n * Zdog v${version}` );\n\nfs.writeFileSync( boilerplatePath, boilerplateSrc, 'utf8' );\n\nconsole.log(`updated ${boilerplatePath} to ${version}`);\n"
  }
]