Browse Source

first real commit

Gregg Tavares 7 years ago
parent
commit
f28c0f9adb
100 changed files with 4285 additions and 0 deletions
  1. 18 0
      .travis.yml
  2. 1 0
      3rdparty/jquery-3.3.1.slim.min.js
  3. 70 0
      Gruntfile.js
  4. 85 0
      build/conf/eslint-examples.json
  5. 86 0
      build/conf/eslint.json
  6. 599 0
      build/js/build.js
  7. 68 0
      build/js/utils.js
  8. 10 0
      build/templates/analytics.template
  9. 9 0
      build/templates/diagram.template
  10. 5 0
      build/templates/example.template
  11. 10 0
      build/templates/header.template
  12. 9 0
      build/templates/image.template
  13. 158 0
      build/templates/index.template
  14. 2 0
      build/templates/lang-select.template
  15. 6 0
      build/templates/languages.template
  16. 108 0
      build/templates/lesson.template
  17. 83 0
      build/templates/missing.template
  18. 8 0
      contributors.md
  19. 6 0
      monaco-editor/min/vs/base/worker/workerMain.js
  20. 7 0
      monaco-editor/min/vs/basic-languages/src/bat.js
  21. 6 0
      monaco-editor/min/vs/basic-languages/src/coffee.js
  22. 6 0
      monaco-editor/min/vs/basic-languages/src/cpp.js
  23. 6 0
      monaco-editor/min/vs/basic-languages/src/csharp.js
  24. 6 0
      monaco-editor/min/vs/basic-languages/src/css.js
  25. 7 0
      monaco-editor/min/vs/basic-languages/src/dockerfile.js
  26. 6 0
      monaco-editor/min/vs/basic-languages/src/fsharp.js
  27. 6 0
      monaco-editor/min/vs/basic-languages/src/go.js
  28. 6 0
      monaco-editor/min/vs/basic-languages/src/handlebars.js
  29. 6 0
      monaco-editor/min/vs/basic-languages/src/html.js
  30. 7 0
      monaco-editor/min/vs/basic-languages/src/ini.js
  31. 6 0
      monaco-editor/min/vs/basic-languages/src/java.js
  32. 6 0
      monaco-editor/min/vs/basic-languages/src/less.js
  33. 6 0
      monaco-editor/min/vs/basic-languages/src/lua.js
  34. 6 0
      monaco-editor/min/vs/basic-languages/src/markdown.js
  35. 6 0
      monaco-editor/min/vs/basic-languages/src/msdax.js
  36. 6 0
      monaco-editor/min/vs/basic-languages/src/objective-c.js
  37. 6 0
      monaco-editor/min/vs/basic-languages/src/php.js
  38. 6 0
      monaco-editor/min/vs/basic-languages/src/postiats.js
  39. 6 0
      monaco-editor/min/vs/basic-languages/src/powershell.js
  40. 6 0
      monaco-editor/min/vs/basic-languages/src/pug.js
  41. 6 0
      monaco-editor/min/vs/basic-languages/src/python.js
  42. 6 0
      monaco-editor/min/vs/basic-languages/src/r.js
  43. 6 0
      monaco-editor/min/vs/basic-languages/src/razor.js
  44. 6 0
      monaco-editor/min/vs/basic-languages/src/ruby.js
  45. 6 0
      monaco-editor/min/vs/basic-languages/src/scss.js
  46. 6 0
      monaco-editor/min/vs/basic-languages/src/solidity.js
  47. 6 0
      monaco-editor/min/vs/basic-languages/src/sql.js
  48. 9 0
      monaco-editor/min/vs/basic-languages/src/swift.js
  49. 6 0
      monaco-editor/min/vs/basic-languages/src/vb.js
  50. 7 0
      monaco-editor/min/vs/basic-languages/src/xml.js
  51. 6 0
      monaco-editor/min/vs/basic-languages/src/yaml.js
  52. 0 0
      monaco-editor/min/vs/editor/contrib/suggest/browser/media/String_16x.svg
  53. 0 0
      monaco-editor/min/vs/editor/contrib/suggest/browser/media/String_inverse_16x.svg
  54. 5 0
      monaco-editor/min/vs/editor/editor.main.css
  55. 6 0
      monaco-editor/min/vs/editor/editor.main.js
  56. 6 0
      monaco-editor/min/vs/editor/editor.main.nls.de.js
  57. 6 0
      monaco-editor/min/vs/editor/editor.main.nls.es.js
  58. 6 0
      monaco-editor/min/vs/editor/editor.main.nls.fr.js
  59. 6 0
      monaco-editor/min/vs/editor/editor.main.nls.hu.js
  60. 6 0
      monaco-editor/min/vs/editor/editor.main.nls.it.js
  61. 6 0
      monaco-editor/min/vs/editor/editor.main.nls.ja.js
  62. 6 0
      monaco-editor/min/vs/editor/editor.main.nls.js
  63. 6 0
      monaco-editor/min/vs/editor/editor.main.nls.ko.js
  64. 6 0
      monaco-editor/min/vs/editor/editor.main.nls.pt-br.js
  65. 6 0
      monaco-editor/min/vs/editor/editor.main.nls.ru.js
  66. 6 0
      monaco-editor/min/vs/editor/editor.main.nls.tr.js
  67. 6 0
      monaco-editor/min/vs/editor/editor.main.nls.zh-cn.js
  68. 6 0
      monaco-editor/min/vs/editor/editor.main.nls.zh-tw.js
  69. 0 0
      monaco-editor/min/vs/editor/standalone/browser/quickOpen/symbol-sprite.svg
  70. 6 0
      monaco-editor/min/vs/language/css/cssMode.js
  71. 6 0
      monaco-editor/min/vs/language/css/cssWorker.js
  72. 6 0
      monaco-editor/min/vs/language/html/htmlMode.js
  73. 6 0
      monaco-editor/min/vs/language/html/htmlWorker.js
  74. 6 0
      monaco-editor/min/vs/language/json/jsonMode.js
  75. 6 0
      monaco-editor/min/vs/language/json/jsonWorker.js
  76. 20 0
      monaco-editor/min/vs/language/typescript/lib/typescriptServices.js
  77. 6 0
      monaco-editor/min/vs/language/typescript/src/mode.js
  78. 6 0
      monaco-editor/min/vs/language/typescript/src/worker.js
  79. 6 0
      monaco-editor/min/vs/loader.js
  80. 3 0
      robots.txt
  81. 194 0
      threejs/background.html
  82. 15 0
      threejs/lessons/index.md
  83. 13 0
      threejs/lessons/langinfo.hanson
  84. BIN
      threejs/lessons/resources/avatar-icon.png
  85. BIN
      threejs/lessons/resources/banner-00.jpg
  86. BIN
      threejs/lessons/resources/banner-00.png
  87. 44 0
      threejs/lessons/resources/index.css
  88. 394 0
      threejs/lessons/resources/lesson.css
  89. 76 0
      threejs/lessons/resources/lesson.js
  90. BIN
      threejs/lessons/resources/logo.png
  91. 1713 0
      threejs/lessons/resources/prettify.js
  92. 18 0
      threejs/lessons/resources/rss-icon.svg
  93. 69 0
      threejs/lessons/resources/threejs-lessons.css
  94. BIN
      threejs/lessons/resources/threejsfundamentals-icon-256.png
  95. BIN
      threejs/lessons/resources/threejsfundamentals-icon.png
  96. BIN
      threejs/lessons/resources/threejsfundamentals.jpg
  97. 12 0
      threejs/lessons/threejs-fundamentals.md
  98. 9 0
      threejs/lessons/toc.html
  99. 11 0
      threejs/resources/editor-fullscreen-icon.svg
  100. 11 0
      threejs/resources/editor-unfullscreen-icon.svg

+ 18 - 0
.travis.yml

@@ -0,0 +1,18 @@
+language: node_js
+node_js:
+  - "8.4"
+script:
+  - npm run build
+env:
+  global:
+    - COMMIT_AUTHOR_EMAIL: "[email protected]"
+
+deploy:
+  provider: pages
+  skip-cleanup: true
+  github-token: $GITHUB_TOKEN  # Set in the settings page of your repository, as a secure variable
+  keep-history: true
+  local-dir: out
+  on:
+    branch: master
+

File diff suppressed because it is too large
+ 1 - 0
3rdparty/jquery-3.3.1.slim.min.js


+ 70 - 0
Gruntfile.js

@@ -0,0 +1,70 @@
+"use strict";
+
+const path = require('path');
+const fs = require('fs');
+
+module.exports = function(grunt) {
+
+  require('load-grunt-tasks')(grunt);
+
+  const s_ignoreRE = /\.(md|py|sh|enc)$/i;
+  function noMds(filename) {
+    return !s_ignoreRE.test(filename);
+  }
+
+  function notFolder(filename) {
+    return !fs.statSync(filename).isDirectory();
+  }
+
+  function noMdsNoFolders(filename) {
+    return noMds(filename) && notFolder(filename);
+  }
+
+  grunt.initConfig({
+    eslint: {
+      lib: {
+        src: [
+          'threejs/resources/*.js',
+        ],
+        options: {
+          config: 'build/conf/eslint.json',
+          //rulesdir: ['build/rules'],
+        },
+      },
+      examples: {
+        src: [
+          'threejs/*.html',
+        ],
+        options: {
+          configFile: 'build/conf/eslint-examples.json',
+        },
+      },
+    },
+    copy: {
+      main: {
+        files: [
+          { expand: false, src: '*', dest: 'out/', filter: noMdsNoFolders, },
+          { expand: true, src: 'threejs/**', dest: 'out/', filter: noMds, },
+          { expand: true, src: 'monaco-editor/**', dest: 'out/', },
+          { expand: true, src: '3rdparty/**', dest: 'out/', },
+        ],
+      },
+    },
+    clean: [
+      'out/**/*',
+    ],
+  });
+
+  grunt.registerTask('buildlessons', function() {
+    var buildStuff = require('./build/js/build');
+    var finish = this.async();
+    buildStuff().then(function() {
+        finish();
+    }).done();
+  });
+
+  grunt.registerTask('build', ['clean', 'copy', 'buildlessons']);
+
+  grunt.registerTask('default', ['eslint', 'build']);
+};
+

+ 85 - 0
build/conf/eslint-examples.json

@@ -0,0 +1,85 @@
+{
+  "env": {
+    "browser": true,
+    "es6": true
+  },
+  "plugins": [
+    "eslint-plugin-html",
+    "eslint-plugin-optional-comma-spacing",
+    "eslint-plugin-one-variable-per-var",
+    "eslint-plugin-require-trailing-comma"
+  ],
+  "extends": "eslint:recommended",
+  "rules": {
+    "no-alert": 2,
+    "no-array-constructor": 2,
+    "no-caller": 2,
+    "no-catch-shadow": 2,
+    "no-const-assign": 2,
+    "no-eval": 2,
+    "no-extend-native": 2,
+    "no-extra-bind": 2,
+    "no-implied-eval": 2,
+    "no-iterator": 2,
+    "no-label-var": 2,
+    "no-labels": 2,
+    "no-lone-blocks": 0,
+    "no-loop-func": 2,
+    "no-multi-str": 2,
+    "no-native-reassign": 2,
+    "no-new": 2,
+    "no-new-func": 2,
+    "no-new-object": 2,
+    "no-new-wrappers": 2,
+    "no-octal-escape": 2,
+    "no-process-exit": 2,
+    "no-proto": 2,
+    "no-return-assign": 2,
+    "no-script-url": 2,
+    "no-sequences": 2,
+    "no-shadow-restricted-names": 2,
+    "no-spaced-func": 2,
+    "no-trailing-spaces": 2,
+    "no-undef-init": 2,
+    "no-underscore-dangle": 2,
+    "no-unused-expressions": 2,
+    "no-use-before-define": 0,
+    "no-with": 2,
+    "consistent-return": 2,
+    "curly": [2, "all"],
+    "no-extra-parens": [2, "functions"],
+    "eqeqeq": 2,
+    "new-cap": 2,
+    "new-parens": 2,
+    "semi-spacing": [2, {"before": false, "after": true}],
+    "space-infix-ops": 2,
+    "space-unary-ops": [2, { "words": true, "nonwords": false }],
+    "yoda": [2, "never"],
+
+    "brace-style": [2, "1tbs", { "allowSingleLine": false }],
+    "camelcase": [0],
+    "comma-spacing": 0,
+    "comma-dangle": 0,
+    "comma-style": [2, "last"],
+    "optional-comma-spacing/optional-comma-spacing": [2, {"after": true}],
+    "dot-notation": 0,
+    "eol-last": [0],
+    "global-strict": [0],
+    "key-spacing": [0],
+    "no-comma-dangle": [0],
+    "no-irregular-whitespace": 2,
+    "no-multi-spaces": [0],
+    "no-obj-calls": 2,
+    "no-redeclare": [0],
+    "no-shadow": [0],
+    "no-undef": [0],
+    "no-unreachable": 2,
+    "one-variable-per-var/one-variable-per-var": [2],
+    "quotes": [2, "single"],
+    "require-trailing-comma/require-trailing-comma": [2],
+    "semi": [2, "always"],
+    "strict": [2, "global"],
+    "space-before-function-paren": [2, "never"],
+    "keyword-spacing": [1, {"before": true, "after": true, "overrides": {}} ]
+  }
+}

+ 86 - 0
build/conf/eslint.json

@@ -0,0 +1,86 @@
+{
+  "env": {
+    "browser": true,
+    "es6": true
+  },
+  "plugins": [
+    "eslint-plugin-html",
+    "eslint-plugin-optional-comma-spacing",
+    "eslint-plugin-one-variable-per-var",
+    "eslint-plugin-require-trailing-comma"
+  ],
+  "extends": "eslint:recommended",
+  "rules": {
+    "no-alert": 2,
+    "no-array-constructor": 2,
+    "no-caller": 2,
+    "no-catch-shadow": 2,
+    "no-const-assign": 2,
+    "no-labels": 2,
+    "no-eval": 2,
+    "no-extend-native": 2,
+    "no-extra-bind": 2,
+    "no-implied-eval": 2,
+    "no-iterator": 2,
+    "no-label-var": 2,
+    "no-labels": 2,
+    "no-lone-blocks": 2,
+    "no-loop-func": 2,
+    "no-multi-str": 2,
+    "no-native-reassign": 2,
+    "no-new": 2,
+    "no-new-func": 2,
+    "no-new-object": 2,
+    "no-new-wrappers": 2,
+    "no-octal-escape": 2,
+    "no-process-exit": 2,
+    "no-proto": 2,
+    "no-return-assign": 2,
+    "no-script-url": 2,
+    "no-sequences": 2,
+    "no-shadow-restricted-names": 2,
+    "no-spaced-func": 2,
+    "no-trailing-spaces": 2,
+    "no-undef-init": 2,
+    "no-underscore-dangle": 2,
+    "no-unused-expressions": 2,
+    "no-use-before-define": 0,
+    "no-with": 2,
+    "consistent-return": 2,
+    "curly": [2, "all"],
+    "no-extra-parens": [2, "functions"],
+    "eqeqeq": 2,
+    "new-cap": 2,
+    "new-parens": 2,
+    "semi-spacing": [2, {"before": false, "after": true}],
+    "space-infix-ops": 2,
+    "space-unary-ops": [2, { "words": true, "nonwords": false }],
+    "strict": [2, "function"],
+    "yoda": [2, "never"],
+
+    "brace-style": [2, "1tbs", { "allowSingleLine": false }],
+    "camelcase": [0],
+    "comma-spacing": 0,
+    "comma-dangle": 0,
+    "comma-style": [2, "last"],
+    "dot-notation": 0,
+    "eol-last": [0],
+    "global-strict": [0],
+    "key-spacing": [0],
+    "no-comma-dangle": [0],
+    "no-irregular-whitespace": 2,
+    "no-multi-spaces": [0],
+    "no-obj-calls": 2,
+    "no-shadow": [0],
+    "no-undef": [0],
+    "no-unreachable": 2,
+    "one-variable-per-var/one-variable-per-var": [2],
+    "optional-comma-spacing/optional-comma-spacing": [2, {"after": true}],
+    "quotes": [0, "single"],
+    "require-trailing-comma/require-trailing-comma": [2],
+    "semi": [2, "always"],
+    "space-before-function-paren": [2, "never"],
+    "keyword-spacing": [1, {"before": true, "after": true, "overrides": {}} ]
+  }
+
+}

+ 599 - 0
build/js/build.js

@@ -0,0 +1,599 @@
+
+module.exports = function () { // wrapper in case we're in module_context mode
+
+"use strict";
+
+const args       = require('minimist')(process.argv.slice(2));
+const cache      = new (require('inmemfilecache'));
+const Feed       = require('feed');
+const fs         = require('fs');
+const glob       = require('glob');
+const Handlebars = require('handlebars');
+const hanson     = require('hanson');
+const marked     = require('marked');
+const path       = require('path');
+const Promise    = require('promise');
+const sitemap    = require('sitemap');
+const utils      = require('./utils');
+const moment     = require('moment');
+const url        = require('url');
+
+//process.title = "build";
+
+var executeP = Promise.denodeify(utils.execute);
+
+marked.setOptions({
+  rawHtml: true,
+  //pedantic: true,
+});
+
+function applyObject(src, dst) {
+  Object.keys(src).forEach(function(key) {
+    dst[key] = src[key];
+  });
+  return dst;
+}
+
+function mergeObjects() {
+  var merged = {};
+  Array.prototype.slice.call(arguments).forEach(function(src) {
+    applyObject(src, merged);
+  });
+  return merged;
+}
+
+function readFile(fileName) {
+  return cache.readFileSync(fileName, "utf-8");
+}
+
+function writeFileIfChanged(fileName, content) {
+  if (fs.existsSync(fileName)) {
+    var old = readFile(fileName);
+    if (content == old) {
+      return;
+    }
+  }
+  fs.writeFileSync(fileName, content);
+  console.log("Wrote: " + fileName);
+};
+
+function copyFile(src, dst) {
+  writeFileIfChanged(dst, readFile(src));
+}
+
+function replaceParams(str, params) {
+  var template = Handlebars.compile(str);
+  if (Array.isArray(params)) {
+    params = mergeObjects.apply(null, params.slice().reverse());
+  }
+
+  return template(params);
+}
+
+function encodeQuery(query) {
+  if (!query) {
+    return '';
+  }
+  return '?' + query.split("&").map(function(pair) {
+    return pair.split("=").map(function (kv) {
+      return encodeURIComponent(decodeURIComponent(kv));
+    }).join('=');
+  }).join('&');
+}
+
+function encodeUrl(src) {
+  const u = url.parse(src);
+  u.search = encodeQuery(u.query);
+  return url.format(u);
+}
+
+function TemplateManager() {
+  var templates = {};
+
+  this.apply = function(filename, params) {
+    var template = templates[filename];
+    if (!template) {
+      var template = Handlebars.compile(readFile(filename));
+      templates[filename] = template;
+    }
+
+    if (Array.isArray(params)) {
+      params = mergeObjects.apply(null, params.slice().reverse());
+    }
+
+    return template(params);
+  };
+}
+
+var templateManager = new TemplateManager();
+
+Handlebars.registerHelper('include', function(filename, options) {
+  var context;
+  if (options && options.hash && options.hash.filename) {
+    var varName = options.hash.filename;
+    filename = options.data.root[varName];
+    context = options.hash;
+  } else {
+    context = options.data.root;
+  }
+  return templateManager.apply(filename, context);
+});
+
+Handlebars.registerHelper('example', function(options) {
+  options.hash.width   = options.hash.width  ? "width:  " + options.hash.width  + "px;" : "";
+  options.hash.height  = options.hash.height ? "height: " + options.hash.height + "px;" : "";
+  options.hash.caption = options.hash.caption || options.data.root.defaultExampleCaption;
+  options.hash.examplePath = options.data.root.examplePath;
+  options.hash.encodedUrl = encodeURIComponent(encodeUrl(options.hash.url));
+  options.hash.url = encodeUrl(options.hash.url);
+  return templateManager.apply("build/templates/example.template", options.hash);
+});
+
+Handlebars.registerHelper('diagram', function(options) {
+
+  options.hash.width  = options.hash.width || "400";
+  options.hash.height = options.hash.height || "300";
+  options.hash.examplePath = options.data.root.examplePath;
+  options.hash.className = options.hash.className || "";
+  options.hash.url = encodeUrl(options.hash.url);
+
+  return templateManager.apply("build/templates/diagram.template", options.hash);
+});
+
+Handlebars.registerHelper('image', function(options) {
+
+  options.hash.examplePath = options.data.root.examplePath;
+  options.hash.className = options.hash.className || "";
+  options.hash.caption = options.hash.caption || "";
+
+  if (options.hash.url.substring(0, 4) === 'http') {
+    options.hash.examplePath = "";
+  }
+
+  return templateManager.apply("build/templates/image.template", options.hash);
+});
+
+Handlebars.registerHelper('selected', function(options) {
+  const key = options.hash.key;
+  const value = options.hash.value;
+  const re = options.hash.re;
+  const sub = options.hash.sub;
+
+  let a = this[key];
+  let b = options.data.root[value];
+
+  if (re) {
+    const r = new RegExp(re);
+    b = b.replace(r, sub);
+  }
+
+  return a === b ? 'selected' : '';
+});
+
+function slashify(s) {
+  return s.replace(/\\/g, '/');
+}
+
+var Builder = function(outBaseDir, options) {
+
+  var g_articlesByLang = {};
+  var g_articles = [];
+  var g_langInfo;
+  var g_langDB = {};
+  var g_outBaseDir = outBaseDir;
+  var g_origPath = options.origPath;
+
+  // This are the english articles.
+  var g_origArticles = glob.sync(path.join(g_origPath, "*.md")).map(a => path.basename(a)).filter(a => a !== 'index.md');
+
+  var extractHeader = (function() {
+    var headerRE = /([A-Z0-9_-]+): (.*?)$/i;
+
+    return function(content) {
+      var metaData = { };
+      var lines = content.split("\n");
+      while (true) {
+        var line = lines[0].trim();
+        var m = headerRE.exec(line);
+        if (!m) {
+          break;
+        }
+        metaData[m[1].toLowerCase()] = m[2];
+        lines.shift();
+      }
+      return {
+        content: lines.join("\n"),
+        headers: metaData,
+      };
+    };
+  }());
+
+  var parseMD = function(content) {
+    return extractHeader(content);
+  };
+
+  var loadMD = function(contentFileName) {
+    var content = cache.readFileSync(contentFileName, "utf-8");
+    return parseMD(content);
+  };
+
+  function extractHandlebars(content) {
+    var tripleRE = /\{\{\{.*?\}\}\}/g;
+    var doubleRE = /\{\{\{.*?\}\}\}/g;
+
+    var numExtractions = 0;
+    var extractions = {
+    };
+
+    function saveHandlebar(match) {
+      var id = "==HANDLEBARS_ID_" + (++numExtractions) + "==";
+      extractions[id] = match;
+      return id;
+    }
+
+    content = content.replace(tripleRE, saveHandlebar);
+    content = content.replace(doubleRE, saveHandlebar);
+
+    return {
+      content: content,
+      extractions: extractions,
+    };
+  }
+
+  function insertHandlebars(info, content) {
+    var handlebarRE = /==HANDLEBARS_ID_\d+==/g;
+
+    function restoreHandlebar(match) {
+      var value = info.extractions[match];
+      if (value === undefined) {
+        throw new Error("no match restoring handlebar for: " + match);
+      }
+      return value;
+    }
+
+    content = content.replace(handlebarRE, restoreHandlebar);
+
+    return content;
+  }
+
+  var applyTemplateToContent = function(templatePath, contentFileName, outFileName, opt_extra, data) {
+    // Call prep's Content which parses the HTML. This helps us find missing tags
+    // should probably call something else.
+    //Convert(md_content)
+    var metaData = data.headers;
+    var content = data.content;
+    //console.log(JSON.stringify(metaData, undefined, "  "));
+    var info = extractHandlebars(content);
+    var html = marked(info.content);
+    html = insertHandlebars(info, html);
+    html = replaceParams(html, [opt_extra, g_langInfo]);
+    const relativeOutName = slashify(outFileName).substring(g_outBaseDir.length);
+    const langs = Object.keys(g_langDB).map((name) => {
+      const lang = g_langDB[name];
+      const url = slashify(path.join(lang.basePath, path.basename(outFileName)))
+         .replace("index.html", "")
+         .replace(/^\/threejs\/lessons\/$/, '/');
+      return {
+        lang: lang.lang,
+        language: lang.language,
+        url: url,
+      };
+    });
+    metaData['content'] = html;
+    metaData['langs'] = langs;
+    metaData['src_file_name'] = slashify(contentFileName);
+    metaData['dst_file_name'] = relativeOutName;
+    metaData['basedir'] = "";
+    metaData['toc'] = opt_extra.toc;
+    metaData['templateOptions'] = opt_extra.templateOptions;
+    metaData['langInfo'] = g_langInfo;
+    metaData['url'] = "http://threejsfundamentals.org" + relativeOutName;
+    metaData['relUrl'] = relativeOutName;
+    metaData['screenshot'] = "http://threejsfundamentals.org/threejs/lessons/resources/threejsfundamentals.jpg";
+    var basename = path.basename(contentFileName, ".md");
+    [".jpg", ".png"].forEach(function(ext) {
+      var filename = path.join("threejs", "lessons", "screenshots", basename + ext);
+      if (fs.existsSync(filename)) {
+        metaData['screenshot'] = "http://threejsfundamentals.org/threejs/lessons/screenshots/" + basename + ext;
+      }
+    });
+    var output = templateManager.apply(templatePath, metaData);
+    writeFileIfChanged(outFileName, output);
+
+    return metaData;
+  };
+
+  var applyTemplateToFile = function(templatePath, contentFileName, outFileName, opt_extra) {
+    console.log("processing: ", contentFileName);
+    opt_extra = opt_extra || {};
+    var data = loadMD(contentFileName);
+    var metaData= applyTemplateToContent(templatePath, contentFileName, outFileName, opt_extra, data);
+    g_articles.push(metaData);
+  };
+
+  var applyTemplateToFiles = function(templatePath, filesSpec, extra) {
+    var files = glob.sync(filesSpec).sort();
+    files.forEach(function(fileName) {
+      var ext = path.extname(fileName);
+      var baseName = fileName.substr(0, fileName.length - ext.length);
+      var outFileName = path.join(outBaseDir, baseName + ".html");
+      applyTemplateToFile(templatePath, fileName, outFileName, extra);
+    });
+
+  };
+
+  var addArticleByLang = function(article, lang) {
+    var filename = path.basename(article.dst_file_name);
+    var articleInfo = g_articlesByLang[filename];
+    var url = "http://threejsfundamentals.org" + article.dst_file_name;
+    if (!articleInfo) {
+      articleInfo = {
+        url: url,
+        changefreq: 'monthly',
+        links: [],
+      };
+      g_articlesByLang[filename] = articleInfo;
+    }
+    articleInfo.links.push({
+      url: url,
+      lang: lang,
+    });
+  };
+
+  var getLanguageSelection = function(lang) {
+    var lessons = lang.lessons || ("threejs/lessons/" + lang.lang);
+    var langInfo = hanson.parse(fs.readFileSync(path.join(lessons, "langinfo.hanson"), {encoding: "utf8"}));
+    langInfo.langCode = langInfo.langCode || lang.lang;
+    langInfo.home = lang.home || ('/' + lessons + '/');
+    g_langDB[lang.lang] = {
+      lang: lang.lang,
+      language: langInfo.language,
+      basePath: '/' + lessons,
+      langInfo: langInfo,
+    };
+  };
+
+  this.preProcess = function(langs) {
+     langs.forEach(getLanguageSelection);
+  };
+
+  this.process = function(options) {
+    console.log("Processing Lang: " + options.lang);
+    options.lessons     = options.lessons     || ("threejs/lessons/" + options.lang);
+    options.toc         = options.toc         || ("threejs/lessons/" + options.lang + "/toc.html");
+    options.template    = options.template    || "build/templates/lesson.template";
+    options.examplePath = options.examplePath === undefined ? "/threejs/lessons/" : options.examplePath;
+
+    g_articles = [];
+    g_langInfo = g_langDB[options.lang].langInfo;
+
+    applyTemplateToFiles(options.template, path.join(options.lessons, "threejs*.md"), options);
+
+    // generate place holders for non-translated files
+    var articlesFilenames = g_articles.map(a => path.basename(a.src_file_name));
+    var missing = g_origArticles.filter(name => articlesFilenames.indexOf(name) < 0);
+    missing.forEach(name => {
+      const ext = path.extname(name);
+      const baseName = name.substr(0, name.length - ext.length);
+      const outFileName = path.join(outBaseDir, options.lessons, baseName + ".html");
+      const data = Object.assign({}, loadMD(path.join(g_origPath, name)));
+      data.content = g_langInfo.missing;
+      const extra = {
+        origLink: '/' + slashify(path.join(g_origPath, baseName + ".html")),
+        toc: options.toc,
+      };
+      console.log("  generating missing:", outFileName);
+      applyTemplateToContent(
+          "build/templates/missing.template",
+          path.join(options.lessons, "langinfo.hanson"),
+          outFileName,
+          extra,
+          data);
+    });
+
+    function utcMomentFromGitLog(result) {
+      const dateStr = result.stdout.split("\n")[0].trim();
+      let utcDateStr = dateStr
+        .replace(/"/g, "")   // WTF to these quotes come from!??!
+        .replace(" ", "T")
+        .replace(" ", "")
+        .replace(/(\d\d)$/, ':$1');
+      return moment.utc(utcDateStr);
+    }
+
+    const tasks = g_articles.map((article, ndx) => {
+      return function() {
+        return executeP('git', [
+          'log',
+          '--format="%ci"',
+          '--name-only',
+          '--diff-filter=A',
+          article.src_file_name,
+        ]).then((result) => {
+          article.dateAdded = utcMomentFromGitLog(result);
+        });
+      };
+    }).concat(g_articles.map((article, ndx) => {
+       return function() {
+         return executeP('git', [
+           'log',
+           '--format="%ci"',
+           '--name-only',
+           '--max-count=1',
+           article.src_file_name,
+         ]).then((result) => {
+           article.dateModified = utcMomentFromGitLog(result);
+         });
+       };
+    }));
+
+    return tasks.reduce(function(cur, next){
+        return cur.then(next);
+    }, Promise.resolve()).then(function() {
+      var articles = g_articles.filter(function(article) {
+        return article.dateAdded != undefined;
+      });
+      articles = articles.sort(function(a, b) {
+        return b.dateAdded - a.dateAdded;
+      });
+
+      var feed = new Feed({
+        title:          g_langInfo.title,
+        description:    g_langInfo.description,
+        link:           g_langInfo.link,
+        image:          'http://threejsfundamentals.org/threejs/lessons/resources/threejsfundamentals.jpg',
+        date:           articles[0].dateModified.toDate(),
+        published:      articles[0].dateModified.toDate(),
+        updated:        articles[0].dateModified.toDate(),
+        author: {
+          name:       'threejsfundamenals contributors',
+          link:       'http://threejsfundamentals.org/contributors.html',
+        },
+      });
+
+      articles.forEach(function(article, ndx) {
+        feed.addItem({
+          title:          article.title,
+          link:           "http://threejsfundamentals.org" + article.dst_file_name,
+          description:    "",
+          author: [
+            {
+              name:       'threejsfundamenals contributors',
+              link:       'http://threejsfundamentals.org/contributors.html',
+            },
+          ],
+          // contributor: [
+          // ],
+          date:           article.dateModified.toDate(),
+          published:      article.dateAdded.toDate(),
+          // image:          posts[key].image
+        });
+
+        addArticleByLang(article, options.lang);
+      });
+
+      try {
+        const outPath = path.join(g_outBaseDir, options.lessons, "atom.xml");
+        console.log("write:", outPath);
+        writeFileIfChanged(outPath, feed.render('atom-1.0'));
+      } catch (err) {
+        return Promise.reject(err);
+      }
+      return Promise.resolve();
+    }).then(function() {
+      // this used to insert a table of contents
+      // but it was useless being auto-generated
+      applyTemplateToFile("build/templates/index.template", path.join(options.lessons, "index.md"), path.join(g_outBaseDir, options.lessons, "index.html"), {
+        table_of_contents: "",
+        templateOptions: g_langInfo,
+      });
+      return Promise.resolve();
+    }, function(err) {
+      console.error("ERROR!:");
+      console.error(err);
+      if (err.stack) {
+        console.error(err.stack);
+      }
+      throw new Error(err.toString());
+    });
+  }
+
+  this.writeGlobalFiles = function() {
+    var sm = sitemap.createSitemap ({
+      hostname: 'http://threejsfundamentals.org',
+      cacheTime: 600000,
+    });
+    var articleLangs = { };
+    Object.keys(g_articlesByLang).forEach(function(filename) {
+      var article = g_articlesByLang[filename];
+      var langs = {};
+      article.links.forEach(function(link) {
+        langs[link.lang] = true;
+      });
+      articleLangs[filename] = langs;
+      sm.add(article);
+    });
+    // var langInfo = {
+    //   articles: articleLangs,
+    //   langs: g_langDB,
+    // };
+    // var langJS = "window.langDB = " + JSON.stringify(langInfo, null, 2);
+    // writeFileIfChanged(path.join(g_outBaseDir, "langdb.js"), langJS);
+    writeFileIfChanged(path.join(g_outBaseDir, "sitemap.xml"), sm.toString());
+    copyFile(path.join(g_outBaseDir, "threejs/lessons/atom.xml"), path.join(g_outBaseDir, "atom.xml"));
+    copyFile(path.join(g_outBaseDir, "threejs/lessons/index.html"), path.join(g_outBaseDir, "index.html"));
+
+    applyTemplateToFile("build/templates/index.template", "contributors.md", path.join(g_outBaseDir, "contributors.html"), {
+      table_of_contents: "",
+      templateOptions: "",
+    });
+  };
+
+
+};
+
+var b = new Builder("out", {
+  origPath: "threejs/lessons",  // english articles
+});
+
+var readdirs = function(dirpath) {
+  var dirsOnly = function(filename) {
+    var stat = fs.statSync(filename);
+    return stat.isDirectory();
+  };
+
+  var addPath = function(filename) {
+    return path.join(dirpath, filename);
+  };
+
+  return fs.readdirSync("threejs/lessons")
+      .map(addPath)
+      .filter(dirsOnly);
+};
+
+var isLangFolder = function(dirname) {
+  var filename = path.join(dirname, "langinfo.hanson");
+  return fs.existsSync(filename);
+};
+
+
+var pathToLang = function(filename) {
+  return {
+    lang: path.basename(filename),
+  };
+};
+
+var langs = [
+  // English is special (sorry it's where I started)
+  {
+    template: "build/templates/lesson.template",
+    lessons: "threejs/lessons",
+    lang: 'en',
+    toc: 'threejs/lessons/toc.html',
+    examplePath: '/threejs/lessons/',
+    home: '/',
+  },
+];
+
+langs = langs.concat(readdirs("threejs/lessons")
+    .filter(isLangFolder)
+    .map(pathToLang));
+
+b.preProcess(langs);
+
+var tasks = langs.map(function(lang) {
+  return function() {
+    return b.process(lang);
+  };
+});
+
+return tasks.reduce(function(cur, next) {
+  return cur.then(next);
+}, Promise.resolve()).then(function() {
+  b.writeGlobalFiles();
+  cache.clear();
+  return Promise.resolve();
+});
+
+};
+

+ 68 - 0
build/js/utils.js

@@ -0,0 +1,68 @@
+/*
+ * Copyright 2014, Gregg Tavares.
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are
+ * met:
+ *
+ *     * Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ *     * Redistributions in binary form must reproduce the above
+ * copyright notice, this list of conditions and the following disclaimer
+ * in the documentation and/or other materials provided with the
+ * distribution.
+ *     * Neither the name of Gregg Tavares. nor the names of its
+ * contributors may be used to endorse or promote products derived from
+ * this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+ * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+"use strict";
+
+var fs = require('fs');
+var path = require('path');
+
+var execute = function(cmd, args, callback) {
+  var spawn = require('child_process').spawn;
+
+  var proc = spawn(cmd, args);
+  var stdout = [];
+  var stderr = [];
+
+  proc.stdout.setEncoding('utf8');
+  proc.stdout.on('data', function (data) {
+      var str = data.toString()
+      var lines = str.split(/(\r?\n)/g);
+      stdout = stdout.concat(lines);
+  });
+
+  proc.stderr.setEncoding('utf8');
+  proc.stderr.on('data', function (data) {
+      var str = data.toString()
+      var lines = str.split(/(\r?\n)/g);
+      stderr = stderr.concat(lines);
+  });
+
+  proc.on('close', function (code) {
+    var result = {stdout: stdout.join("\n"), stderr: stderr.join("\n")};
+    if (parseInt(code) != 0) {
+      callback("exit code " + code, result)
+    } else {
+      callback(null, result)
+    }
+  });
+}
+
+exports.execute = execute;
+

+ 10 - 0
build/templates/analytics.template

@@ -0,0 +1,10 @@
+<script src="//cdn.webglstats.com/stat.js" defer="defer" async="async"></script>
+<script async src="https://www.googletagmanager.com/gtag/js?id=UA-120733518-1"></script>
+<script>
+  window.dataLayer = window.dataLayer || [];
+  function gtag(){dataLayer.push(arguments);}
+  gtag('js', new Date());
+
+  gtag('config', 'UA-120733518-1');
+</script>
+

+ 9 - 0
build/templates/diagram.template

@@ -0,0 +1,9 @@
+<div class="threejs_diagram_container">
+  <iframe class="threejs_example {{className}}" style="width: {{width}}px; height: {{height}}px;" src="{{{examplePath}}}{{{url}}}"></iframe>
+  {{#caption}}
+  <div class="threejs_center">{{{../caption}}}</div>
+  {{/caption}}
+  {{^caption}}
+  {{/caption}}
+</div>
+

+ 5 - 0
build/templates/example.template

@@ -0,0 +1,5 @@
+<div class="threejs_example_container">
+  <iframe class="threejs_example" style="{{width}} {{height}}" src="/threejs/resources/editor.html?url={{{examplePath}}}{{{encodedUrl}}}"></iframe>
+  <a class="threejs_center" href="{{{examplePath}}}{{{url}}}" target="_blank">{{{caption}}}</a>
+</div>
+

+ 10 - 0
build/templates/header.template

@@ -0,0 +1,10 @@
+<div class="threejs_navbar">
+  <div>
+    {{{include "build/templates/languages.template"}}}
+    <a href="#toc">{{langInfo.toc}}</a>
+  </div>
+</div>
+<div class="threejs_header">
+  <h1><a href="{{langInfo.home}}">threejsfundamentals.org</a></h1>
+</div>
+

+ 9 - 0
build/templates/image.template

@@ -0,0 +1,9 @@
+<div class="threejs_image {{className}}">
+  <img class="" src="{{{examplePath}}}{{{url}}}">
+  {{#caption}}
+  <div class="threejs_center">{{{../caption}}}</div>
+  {{/caption}}
+  {{^caption}}
+  {{/caption}}
+</div>
+

+ 158 - 0
build/templates/index.template

@@ -0,0 +1,158 @@
+<!DOCTYPE html>
+<!-- this file is auto-generated from {{src_file_name}}. Do not edited directly -->
+<!--
+Copyright 2018, Google Inc.
+All rights reserved.
+
+Redistribution and use in source and binary forms, with or without
+modification, are permitted provided that the following conditions are
+met:
+
+*   Redistributions of source code must retain the above copyright
+    notice, this list of conditions and the following disclaimer.
+
+*   Redistributions in binary form must reproduce the above
+    copyright notice, this list of conditions and the following disclaimer
+    in the documentation and/or other materials provided with the
+    distribution.
+
+*   Neither the name of Google Inc. nor the names of their
+    contributors may be used to endorse or promote products derived from
+    this software without specific prior written permission.
+
+THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+-->
+<html lang="{{langInfo.langCode}}">
+<head>
+<meta charset="utf-8">
+
+<meta name="viewport" content="width=device-width, initial-scale=1.0, user-scalable=yes">
+<meta property="og:title" content="{{title}}" />
+<meta property="og:type" content="website" />
+<meta property="og:image" content="https://threejsfundamentals.org/threejs/lessons/resources/threejsfundamentals.jpg" />
+<meta property="og:description" content="{{templateOptions.description}}" />
+<meta property="og:url" content="https://threejsfundamentals.org">
+
+<meta name="twitter:card" content="summary_large_image">
+<meta name="twitter:site" content="@greggman">
+<meta name="twitter:creator" content="@greggman">
+<meta name="twitter:domain" content="threejsfundamentals.org">
+<meta name="twitter:title" content="threejsfundamentals.org">
+<meta name="twitter:url" content="https://threejsfundamentals.org">
+<meta name="twitter:description" content="{{templateOptions.description}}" />
+<meta name="twitter:image:src" content="https://threejsfundamentals.org/threejs/lessons/resources/threejsfundamentals.jpg">
+
+<title>{{title}}</title>
+
+<link rel="alternate" type="application/atom+xml" title="threejs fundamentals" href="https://threejsfundamentals.org/atom.xml" />
+
+<link href="/threejs/lessons/resources/threejsfundamentals-icon.png" rel="shortcut icon" type="image/png">
+<link rel="stylesheet" href="/threejs/lessons/resources/lesson.css" type="text/css" />
+<link rel="stylesheet" href="/threejs/lessons/resources/index.css" type="text/css" />
+</head>
+<body>
+<div id="canvas">
+</div>
+<div class="threejs_navbar">
+  <div>
+    {{{include "build/templates/languages.template"}}}
+  </div>
+</div>
+<div class="container">
+  <div class="lesson">
+    <div class="lesson-main">
+      <h1>{{title}} <span class="rss"><a href="http://threejsfundamentals.org/atom.xml"><img src="/threejs/lessons/resources/rss-icon.svg" alt="rss"/></a></span></h1>
+      <div class="home-lang">
+        {{{include "build/templates/languages.template"}}}
+      </div>
+      {{{content}}}
+      <div>
+      3d scene by: <a href="https://sketchfab.com/elsergio217">elsergio217</a>
+      </div>
+    </div>
+  </div>
+</div>
+<iframe class="background" src="threejs/background.html"></iframe>
+<style>
+#forkongithub a {
+    background: #000;
+    color: #fff;
+    text-decoration: none;
+    font-family: arial,sans-serif;
+    text-align: center;
+    font-weight: bold;
+    padding: 5px 40px;
+    font-size: 0.9rem;
+    line-height: 2rem;
+    position: relative;
+    transition: 0.5s;
+    display: block;
+    width: 300px;
+    position: absolute;
+    top: 0;
+    right: 0;
+    transform: translateX(150px) rotate(45deg) translate(10px,70px);
+    box-shadow: 4px 4px 10px rgba(0,0,0,0.8);
+    pointer-events: auto;
+}
+#forkongithub a:hover {
+    background: #c11;
+    color: #fff;
+}
+#forkongithub a::before,#forkongithub a::after {
+    content: "";
+    width: 100%;
+    display: block;
+    position: absolute;
+    top: 1px;
+    left: 0;
+    height: 1px;
+    background: #fff;
+}
+#forkongithub a::after {
+    bottom: 1px;
+    top: auto;
+}
+
+#forkongithub{
+    z-index: 9999;
+    /* needed for firefox */
+    overflow: hidden;
+    width: 300px;
+    height: 300px;
+    position: absolute;
+    right: 0;
+    top: 0;
+    pointer-events: none;
+}}
+@media (max-width: 900px) {
+    #forkongithub a{
+        line-height: 1.2rem;
+    }
+}
+@media (max-width: 410px) {
+    #forkongithub a{
+        font-size: 0.7rem;
+        transform: translateX(150px) rotate(45deg) translate(20px,40px);
+    }
+}
+
+</style>
+<div id="forkongithub"><a href="https://github.com/greggman/threejsfundamentals">Fix or Fork me on GitHub</a></div>
+</body>
+<script src="/3rdparty/jquery-3.3.1.slim.min.js"></script>
+<script src="/threejs/lessons/resources/lesson.js"></script>
+{{{include "build/templates/analytics.template" }}}
+</html>
+
+

+ 2 - 0
build/templates/lang-select.template

@@ -0,0 +1,2 @@
+<option value="{{lang}}">{{language}}</option>
+

+ 6 - 0
build/templates/languages.template

@@ -0,0 +1,6 @@
+<select class="language">
+  {{#each langs}}
+    <option value="{{url}}" {{{selected key="url" value="relUrl" re="index\.html" sub="" }}}>{{language}}</a>
+  {{/each}}
+</select>
+

+ 108 - 0
build/templates/lesson.template

@@ -0,0 +1,108 @@
+<!DOCTYPE html>
+<!-- this file is auto-generated from {{src_file_name}}. Do not edited directly -->
+<!--
+Copyright 2018, Google Inc.
+All rights reserved.
+
+Redistribution and use in source and binary forms, with or without
+modification, are permitted provided that the following conditions are
+met:
+
+*   Redistributions of source code must retain the above copyright
+    notice, this list of conditions and the following disclaimer.
+
+*   Redistributions in binary form must reproduce the above
+    copyright notice, this list of conditions and the following disclaimer
+    in the documentation and/or other materials provided with the
+    distribution.
+
+*   Neither the name of Google Inc. nor the names of their
+    contributors may be used to endorse or promote products derived from
+    this software without specific prior written permission.
+
+THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+-->
+<html lang="{{langInfo.langCode}}">
+<head>
+<meta charset="utf-8">
+<meta name="viewport" content="width=device-width, initial-scale=1.0, user-scalable=yes">
+<meta property="og:title" content="{{title}}" />
+<meta property="og:type" content="website" />
+<meta property="og:image" content="{{screenshot}}" />
+<meta property="og:description" content="{{description}}" />
+<meta property="og:url" content="{{url}}" />
+
+<meta name="twitter:card" content="summary_large_image" />
+<meta name="twitter:site" content="@greggman" />
+<meta name="twitter:creator" content="@greggman" />
+<meta name="twitter:domain" content="threejsfundamentals.org" />
+<meta name="twitter:title" content="{{title}}" />
+<meta name="twitter:url" content="{{url}}" />
+<meta name="twitter:description" content="{{description}}" />
+<meta name="twitter:image:src" content="{{screenshot}}" />
+
+
+<title>{{title}}</title>
+<link href="/threejs/lessons/resources/threejsfundamentals-icon.png" rel="shortcut icon" type="image/png">
+<link rel="stylesheet" href="/threejs/lessons/resources/lesson.css" type="text/css" />
+</head>
+<body>
+{{{include "build/templates/header.template"}}}
+<div class="container">
+  <div class="lesson-title">
+    <h1>{{title}}</h1>
+  </div>
+  <div class="lesson">
+    <div class="lesson-main">
+      {{{content}}}
+    </div>
+    <div class="lesson-sidebar">
+        {{{include "build/templates/languages.template"}}}
+        <div id="toc">
+          {{{include "toc.html" filename="toc"}}}
+        </div>
+    </div>
+    <div class="lesson-comments">
+        {{{langInfo.commentSectionHeader}}}
+
+        <div id="disqus_thread"></div>
+        <script type="text/javascript">
+            /* * * CONFIGURATION VARIABLES: EDIT BEFORE PASTING INTO YOUR WEBPAGE * * */
+            var disqus_shortname = 'threejsfundamentals'; // required: replace example with your forum shortname
+            var disqus_identifier = '{{title}}';
+            var disqus_title = '{{title}}';
+
+            /* * * DON'T EDIT BELOW THIS LINE * * */
+            (function() {
+                if (window.location.hostname.indexOf("threejsfundamentals.org") < 0) {
+                    return;
+                }
+                var dsq = document.createElement('script'); dsq.type = 'text/javascript'; dsq.async = true;
+                dsq.src = '//' + disqus_shortname + '.disqus.com/embed.js';
+                (document.getElementsByTagName('head')[0] || document.getElementsByTagName('body')[0]).appendChild(dsq);
+            })();
+        </script>
+        <noscript>Please enable JavaScript to view the <a href="http://disqus.com/?ref_noscript">comments powered by Disqus.</a></noscript>
+        <a href="http://disqus.com" class="dsq-brlink">comments powered by <span class="logo-disqus">Disqus</span></a>
+    </div>
+  </div>
+</div>
+</body>
+<script src="/3rdparty/jquery-3.3.1.slim.min.js"></script>
+<script src="/threejs/lessons/resources/prettify.js"></script>
+<script src="/threejs/lessons/resources/lesson.js"></script>
+{{{include "build/templates/analytics.template" }}}
+</html>
+
+
+

+ 83 - 0
build/templates/missing.template

@@ -0,0 +1,83 @@
+<!DOCTYPE html>
+<!-- this file is auto-generated from {{src_file_name}}. Do not edited directly -->
+<!--
+Copyright 2018, Google Inc.
+All rights reserved.
+
+Redistribution and use in source and binary forms, with or without
+modification, are permitted provided that the following conditions are
+met:
+
+    * Redistributions of source code must retain the above copyright
+      notice, this list of conditions and the following disclaimer.
+
+    * Redistributions in binary form must reproduce the above
+      copyright notice, this list of conditions and the following disclaimer
+      in the documentation and/or other materials provided with the
+      distribution.
+
+    * Neither the name of Google Inc. nor the names of their
+      contributors may be used to endorse or promote products derived from
+      this software without specific prior written permission.
+
+THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+-->
+<html lang="{{langInfo.langCode}}">
+<head>
+<meta charset="utf-8">
+<meta name="viewport" content="width=device-width, initial-scale=1.0, user-scalable=yes">
+<meta property="og:title" content="{{title}}" />
+<meta property="og:type" content="website" />
+<meta property="og:image" content="{{screenshot}}" />
+<meta property="og:description" content="{{description}}" />
+<meta property="og:url" content="{{url}}" />
+
+<meta name="twitter:card" content="summary_large_image" />
+<meta name="twitter:site" content="@greggman" />
+<meta name="twitter:creator" content="@greggman" />
+<meta name="twitter:domain" content="threejsfundamentals.org" />
+<meta name="twitter:title" content="{{title}}" />
+<meta name="twitter:url" content="{{url}}" />
+<meta name="twitter:description" content="{{description}}" />
+<meta name="twitter:image:src" content="{{screenshot}}" />
+
+
+<title>{{title}}</title>
+<link href="/threejs/lessons/resources/threejsfundamentals-icon.png" rel="shortcut icon" type="image/png">
+<link rel="stylesheet" href="/threejs/lessons/resources/lesson.css" type="text/css" />
+</head>
+<body>
+{{{include "build/templates/header.template"}}}
+<div class="container">
+  <div class="lesson-title">
+    <h1>{{title}}</h1>
+  </div>
+  <div class="lesson">
+    <div class="lesson-main">
+      {{{content}}}
+    </div>
+    <div class="lesson-sidebar">
+        {{{include "build/templates/languages.template"}}}
+        {{{include "toc.html" filename="toc"}}}
+    </div>
+  </div>
+</div>
+</body>
+<script src="/3rdparty/jquery-3.3.1.slim.min.js"></script>
+<script src="/threejs/lessons/resources/prettify.js"></script>
+<script src="/threejs/lessons/resources/lesson.js"></script>
+{{{include "build/templates/analytics.template" }}}
+</html>
+
+
+

+ 8 - 0
contributors.md

@@ -0,0 +1,8 @@
+Threejsfundamentals.org Contributors
+==================================
+
+Threejsfundamentals is brought to you by:
+
+*    Gregg (Greggman) Tavares [games.greggman.com](http://games.greggman.com)
+
+

File diff suppressed because it is too large
+ 6 - 0
monaco-editor/min/vs/base/worker/workerMain.js


+ 7 - 0
monaco-editor/min/vs/basic-languages/src/bat.js

@@ -0,0 +1,7 @@
+/*!-----------------------------------------------------------------------------
+ * Copyright (c) Microsoft Corporation. All rights reserved.
+ * monaco-languages version: 0.8.0(fbdcb70601ea4f81278d62ad15d456807ccaa7fa)
+ * Released under the MIT license
+ * https://github.com/Microsoft/monaco-languages/blob/master/LICENSE.md
+ *-----------------------------------------------------------------------------*/
+define("vs/basic-languages/src/bat",["require","exports"],function(e,s){"use strict";Object.defineProperty(s,"__esModule",{value:!0}),s.conf={comments:{lineComment:"REM"},brackets:[["{","}"],["[","]"],["(",")"]],autoClosingPairs:[{open:"{",close:"}"},{open:"[",close:"]"},{open:"(",close:")"},{open:'"',close:'"'}],surroundingPairs:[{open:"[",close:"]"},{open:"(",close:")"},{open:'"',close:'"'}]},s.language={defaultToken:"",ignoreCase:!0,tokenPostfix:".bat",brackets:[{token:"delimiter.bracket",open:"{",close:"}"},{token:"delimiter.parenthesis",open:"(",close:")"},{token:"delimiter.square",open:"[",close:"]"}],keywords:/call|defined|echo|errorlevel|exist|for|goto|if|pause|set|shift|start|title|not|pushd|popd/,symbols:/[=><!~?&|+\-*\/\^;\.,]+/,escapes:/\\(?:[abfnrtv\\"']|x[0-9A-Fa-f]{1,4}|u[0-9A-Fa-f]{4}|U[0-9A-Fa-f]{8})/,tokenizer:{root:[[/^(\s*)(rem(?:\s.*|))$/,["","comment"]],[/(\@?)(@keywords)(?!\w)/,[{token:"keyword"},{token:"keyword.$2"}]],[/[ \t\r\n]+/,""],[/setlocal(?!\w)/,"keyword.tag-setlocal"],[/endlocal(?!\w)/,"keyword.tag-setlocal"],[/[a-zA-Z_]\w*/,""],[/:\w*/,"metatag"],[/%[^%]+%/,"variable"],[/%%[\w]+(?!\w)/,"variable"],[/[{}()\[\]]/,"@brackets"],[/@symbols/,"delimiter"],[/\d*\.\d+([eE][\-+]?\d+)?/,"number.float"],[/0[xX][0-9a-fA-F_]*[0-9a-fA-F]/,"number.hex"],[/\d+/,"number"],[/[;,.]/,"delimiter"],[/"/,"string",'@string."'],[/'/,"string","@string.'"]],string:[[/[^\\"'%]+/,{cases:{"@eos":{token:"string",next:"@popall"},"@default":"string"}}],[/@escapes/,"string.escape"],[/\\./,"string.escape.invalid"],[/%[\w ]+%/,"variable"],[/%%[\w]+(?!\w)/,"variable"],[/["']/,{cases:{"$#==$S2":{token:"string",next:"@pop"},"@default":"string"}}],[/$/,"string","@popall"]]}}});

File diff suppressed because it is too large
+ 6 - 0
monaco-editor/min/vs/basic-languages/src/coffee.js


File diff suppressed because it is too large
+ 6 - 0
monaco-editor/min/vs/basic-languages/src/cpp.js


File diff suppressed because it is too large
+ 6 - 0
monaco-editor/min/vs/basic-languages/src/csharp.js


File diff suppressed because it is too large
+ 6 - 0
monaco-editor/min/vs/basic-languages/src/css.js


+ 7 - 0
monaco-editor/min/vs/basic-languages/src/dockerfile.js

@@ -0,0 +1,7 @@
+/*!-----------------------------------------------------------------------------
+ * Copyright (c) Microsoft Corporation. All rights reserved.
+ * monaco-languages version: 0.8.0(fbdcb70601ea4f81278d62ad15d456807ccaa7fa)
+ * Released under the MIT license
+ * https://github.com/Microsoft/monaco-languages/blob/master/LICENSE.md
+ *-----------------------------------------------------------------------------*/
+define("vs/basic-languages/src/dockerfile",["require","exports"],function(e,s){"use strict";Object.defineProperty(s,"__esModule",{value:!0}),s.conf={brackets:[["{","}"],["[","]"],["(",")"]],autoClosingPairs:[{open:"{",close:"}"},{open:"[",close:"]"},{open:"(",close:")"},{open:'"',close:'"'},{open:"'",close:"'"}],surroundingPairs:[{open:"{",close:"}"},{open:"[",close:"]"},{open:"(",close:")"},{open:'"',close:'"'},{open:"'",close:"'"}]},s.language={defaultToken:"",tokenPostfix:".dockerfile",instructions:/FROM|MAINTAINER|RUN|EXPOSE|ENV|ADD|VOLUME|LABEL|USER|WORKDIR|COPY|CMD|ENTRYPOINT/,instructionAfter:/ONBUILD/,variableAfter:/ENV/,variable:/\${?[\w]+}?/,tokenizer:{root:[{include:"@whitespace"},{include:"@comment"},[/(@instructionAfter)(\s+)/,["keyword",{token:"",next:"@instructions"}]],["","keyword","@instructions"]],instructions:[[/(@variableAfter)(\s+)([\w]+)/,["keyword","",{token:"variable",next:"@arguments"}]],[/(@instructions)/,"keyword","@arguments"]],arguments:[{include:"@whitespace"},{include:"@strings"},[/(@variable)/,{cases:{"@eos":{token:"variable",next:"@popall"},"@default":"variable"}}],[/\\/,{cases:{"@eos":"","@default":""}}],[/./,{cases:{"@eos":{token:"",next:"@popall"},"@default":""}}]],whitespace:[[/\s+/,{cases:{"@eos":{token:"",next:"@popall"},"@default":""}}]],comment:[[/(^#.*$)/,"comment","@popall"]],strings:[[/'$/,"string","@popall"],[/'/,"string","@stringBody"],[/"$/,"string","@popall"],[/"/,"string","@dblStringBody"]],stringBody:[[/[^\\\$']/,{cases:{"@eos":{token:"string",next:"@popall"},"@default":"string"}}],[/\\./,"string.escape"],[/'$/,"string","@popall"],[/'/,"string","@pop"],[/(@variable)/,"variable"],[/\\$/,"string"],[/$/,"string","@popall"]],dblStringBody:[[/[^\\\$"]/,{cases:{"@eos":{token:"string",next:"@popall"},"@default":"string"}}],[/\\./,"string.escape"],[/"$/,"string","@popall"],[/"/,"string","@pop"],[/(@variable)/,"variable"],[/\\$/,"string"],[/$/,"string","@popall"]]}}});

File diff suppressed because it is too large
+ 6 - 0
monaco-editor/min/vs/basic-languages/src/fsharp.js


File diff suppressed because it is too large
+ 6 - 0
monaco-editor/min/vs/basic-languages/src/go.js


File diff suppressed because it is too large
+ 6 - 0
monaco-editor/min/vs/basic-languages/src/handlebars.js


File diff suppressed because it is too large
+ 6 - 0
monaco-editor/min/vs/basic-languages/src/html.js


+ 7 - 0
monaco-editor/min/vs/basic-languages/src/ini.js

@@ -0,0 +1,7 @@
+/*!-----------------------------------------------------------------------------
+ * Copyright (c) Microsoft Corporation. All rights reserved.
+ * monaco-languages version: 0.8.0(fbdcb70601ea4f81278d62ad15d456807ccaa7fa)
+ * Released under the MIT license
+ * https://github.com/Microsoft/monaco-languages/blob/master/LICENSE.md
+ *-----------------------------------------------------------------------------*/
+define("vs/basic-languages/src/ini",["require","exports"],function(e,n){"use strict";Object.defineProperty(n,"__esModule",{value:!0}),n.conf={comments:{lineComment:"#"},brackets:[["{","}"],["[","]"],["(",")"]],autoClosingPairs:[{open:"{",close:"}"},{open:"[",close:"]"},{open:"(",close:")"},{open:'"',close:'"'},{open:"'",close:"'"}],surroundingPairs:[{open:"{",close:"}"},{open:"[",close:"]"},{open:"(",close:")"},{open:'"',close:'"'},{open:"'",close:"'"}]},n.language={defaultToken:"",tokenPostfix:".ini",escapes:/\\(?:[abfnrtv\\"']|x[0-9A-Fa-f]{1,4}|u[0-9A-Fa-f]{4}|U[0-9A-Fa-f]{8})/,tokenizer:{root:[[/^\[[^\]]*\]/,"metatag"],[/(^\w+)(\s*)(\=)/,["key","","delimiter"]],{include:"@whitespace"},[/\d+/,"number"],[/"([^"\\]|\\.)*$/,"string.invalid"],[/'([^'\\]|\\.)*$/,"string.invalid"],[/"/,"string",'@string."'],[/'/,"string","@string.'"]],whitespace:[[/[ \t\r\n]+/,""],[/^\s*[#;].*$/,"comment"]],string:[[/[^\\"']+/,"string"],[/@escapes/,"string.escape"],[/\\./,"string.escape.invalid"],[/["']/,{cases:{"$#==$S2":{token:"string",next:"@pop"},"@default":"string"}}]]}}});

File diff suppressed because it is too large
+ 6 - 0
monaco-editor/min/vs/basic-languages/src/java.js


File diff suppressed because it is too large
+ 6 - 0
monaco-editor/min/vs/basic-languages/src/less.js


File diff suppressed because it is too large
+ 6 - 0
monaco-editor/min/vs/basic-languages/src/lua.js


File diff suppressed because it is too large
+ 6 - 0
monaco-editor/min/vs/basic-languages/src/markdown.js


File diff suppressed because it is too large
+ 6 - 0
monaco-editor/min/vs/basic-languages/src/msdax.js


File diff suppressed because it is too large
+ 6 - 0
monaco-editor/min/vs/basic-languages/src/objective-c.js


File diff suppressed because it is too large
+ 6 - 0
monaco-editor/min/vs/basic-languages/src/php.js


File diff suppressed because it is too large
+ 6 - 0
monaco-editor/min/vs/basic-languages/src/postiats.js


File diff suppressed because it is too large
+ 6 - 0
monaco-editor/min/vs/basic-languages/src/powershell.js


File diff suppressed because it is too large
+ 6 - 0
monaco-editor/min/vs/basic-languages/src/pug.js


File diff suppressed because it is too large
+ 6 - 0
monaco-editor/min/vs/basic-languages/src/python.js


File diff suppressed because it is too large
+ 6 - 0
monaco-editor/min/vs/basic-languages/src/r.js


File diff suppressed because it is too large
+ 6 - 0
monaco-editor/min/vs/basic-languages/src/razor.js


File diff suppressed because it is too large
+ 6 - 0
monaco-editor/min/vs/basic-languages/src/ruby.js


File diff suppressed because it is too large
+ 6 - 0
monaco-editor/min/vs/basic-languages/src/scss.js


File diff suppressed because it is too large
+ 6 - 0
monaco-editor/min/vs/basic-languages/src/solidity.js


File diff suppressed because it is too large
+ 6 - 0
monaco-editor/min/vs/basic-languages/src/sql.js


File diff suppressed because it is too large
+ 9 - 0
monaco-editor/min/vs/basic-languages/src/swift.js


File diff suppressed because it is too large
+ 6 - 0
monaco-editor/min/vs/basic-languages/src/vb.js


+ 7 - 0
monaco-editor/min/vs/basic-languages/src/xml.js

@@ -0,0 +1,7 @@
+/*!-----------------------------------------------------------------------------
+ * Copyright (c) Microsoft Corporation. All rights reserved.
+ * monaco-languages version: 0.8.0(fbdcb70601ea4f81278d62ad15d456807ccaa7fa)
+ * Released under the MIT license
+ * https://github.com/Microsoft/monaco-languages/blob/master/LICENSE.md
+ *-----------------------------------------------------------------------------*/
+define("vs/basic-languages/src/xml",["require","exports"],function(e,t){"use strict";Object.defineProperty(t,"__esModule",{value:!0}),t.conf={comments:{blockComment:["\x3c!--","--\x3e"]},brackets:[["<",">"]],autoClosingPairs:[{open:"<",close:">"},{open:"'",close:"'"},{open:'"',close:'"'}],surroundingPairs:[{open:"<",close:">"},{open:"'",close:"'"},{open:'"',close:'"'}]},t.language={defaultToken:"",tokenPostfix:".xml",ignoreCase:!0,qualifiedName:/(?:[\w\.\-]+:)?[\w\.\-]+/,tokenizer:{root:[[/[^<&]+/,""],{include:"@whitespace"},[/(<)(@qualifiedName)/,[{token:"delimiter"},{token:"tag",next:"@tag"}]],[/(<\/)(@qualifiedName)(\s*)(>)/,[{token:"delimiter"},{token:"tag"},"",{token:"delimiter"}]],[/(<\?)(@qualifiedName)/,[{token:"delimiter"},{token:"metatag",next:"@tag"}]],[/(<\!)(@qualifiedName)/,[{token:"delimiter"},{token:"metatag",next:"@tag"}]],[/<\!\[CDATA\[/,{token:"delimiter.cdata",next:"@cdata"}],[/&\w+;/,"string.escape"]],cdata:[[/[^\]]+/,""],[/\]\]>/,{token:"delimiter.cdata",next:"@pop"}],[/\]/,""]],tag:[[/[ \t\r\n]+/,""],[/(@qualifiedName)(\s*=\s*)("[^"]*"|'[^']*')/,["attribute.name","","attribute.value"]],[/(@qualifiedName)(\s*=\s*)("[^">?\/]*|'[^'>?\/]*)(?=[\?\/]\>)/,["attribute.name","","attribute.value"]],[/(@qualifiedName)(\s*=\s*)("[^">]*|'[^'>]*)/,["attribute.name","","attribute.value"]],[/@qualifiedName/,"attribute.name"],[/\?>/,{token:"delimiter",next:"@pop"}],[/(\/)(>)/,[{token:"tag"},{token:"delimiter",next:"@pop"}]],[/>/,{token:"delimiter",next:"@pop"}]],whitespace:[[/[ \t\r\n]+/,""],[/<!--/,{token:"comment",next:"@comment"}]],comment:[[/[^<\-]+/,"comment.content"],[/-->/,{token:"comment",next:"@pop"}],[/<!--/,"comment.content.invalid"],[/[<\-]/,"comment.content"]]}}});

File diff suppressed because it is too large
+ 6 - 0
monaco-editor/min/vs/basic-languages/src/yaml.js


File diff suppressed because it is too large
+ 0 - 0
monaco-editor/min/vs/editor/contrib/suggest/browser/media/String_16x.svg


File diff suppressed because it is too large
+ 0 - 0
monaco-editor/min/vs/editor/contrib/suggest/browser/media/String_inverse_16x.svg


File diff suppressed because it is too large
+ 5 - 0
monaco-editor/min/vs/editor/editor.main.css


File diff suppressed because it is too large
+ 6 - 0
monaco-editor/min/vs/editor/editor.main.js


File diff suppressed because it is too large
+ 6 - 0
monaco-editor/min/vs/editor/editor.main.nls.de.js


File diff suppressed because it is too large
+ 6 - 0
monaco-editor/min/vs/editor/editor.main.nls.es.js


File diff suppressed because it is too large
+ 6 - 0
monaco-editor/min/vs/editor/editor.main.nls.fr.js


File diff suppressed because it is too large
+ 6 - 0
monaco-editor/min/vs/editor/editor.main.nls.hu.js


File diff suppressed because it is too large
+ 6 - 0
monaco-editor/min/vs/editor/editor.main.nls.it.js


File diff suppressed because it is too large
+ 6 - 0
monaco-editor/min/vs/editor/editor.main.nls.ja.js


File diff suppressed because it is too large
+ 6 - 0
monaco-editor/min/vs/editor/editor.main.nls.js


File diff suppressed because it is too large
+ 6 - 0
monaco-editor/min/vs/editor/editor.main.nls.ko.js


File diff suppressed because it is too large
+ 6 - 0
monaco-editor/min/vs/editor/editor.main.nls.pt-br.js


File diff suppressed because it is too large
+ 6 - 0
monaco-editor/min/vs/editor/editor.main.nls.ru.js


File diff suppressed because it is too large
+ 6 - 0
monaco-editor/min/vs/editor/editor.main.nls.tr.js


File diff suppressed because it is too large
+ 6 - 0
monaco-editor/min/vs/editor/editor.main.nls.zh-cn.js


File diff suppressed because it is too large
+ 6 - 0
monaco-editor/min/vs/editor/editor.main.nls.zh-tw.js


File diff suppressed because it is too large
+ 0 - 0
monaco-editor/min/vs/editor/standalone/browser/quickOpen/symbol-sprite.svg


File diff suppressed because it is too large
+ 6 - 0
monaco-editor/min/vs/language/css/cssMode.js


File diff suppressed because it is too large
+ 6 - 0
monaco-editor/min/vs/language/css/cssWorker.js


File diff suppressed because it is too large
+ 6 - 0
monaco-editor/min/vs/language/html/htmlMode.js


File diff suppressed because it is too large
+ 6 - 0
monaco-editor/min/vs/language/html/htmlWorker.js


File diff suppressed because it is too large
+ 6 - 0
monaco-editor/min/vs/language/json/jsonMode.js


File diff suppressed because it is too large
+ 6 - 0
monaco-editor/min/vs/language/json/jsonWorker.js


File diff suppressed because it is too large
+ 20 - 0
monaco-editor/min/vs/language/typescript/lib/typescriptServices.js


File diff suppressed because it is too large
+ 6 - 0
monaco-editor/min/vs/language/typescript/src/mode.js


File diff suppressed because it is too large
+ 6 - 0
monaco-editor/min/vs/language/typescript/src/worker.js


File diff suppressed because it is too large
+ 6 - 0
monaco-editor/min/vs/loader.js


+ 3 - 0
robots.txt

@@ -0,0 +1,3 @@
+Sitemap: https://threejsfundamentals.org/sitemap.xml
+
+

+ 194 - 0
threejs/background.html

@@ -0,0 +1,194 @@
+<!-- Licensed under a BSD license. See license.html for license -->
+<!DOCTYPE html>
+<html>
+  <head>
+    <meta charset="utf-8">
+    <meta name="viewport" content="width=device-width, initial-scale=1.0, user-scalable=yes">
+    <title>Three.js - Fundamentals</title>
+    <link type="text/css" href="resources/threejs-tutorials.css" rel="stylesheet" />
+    <style>
+    html, body {
+      margin: 0;
+      height: 100%;
+    }
+    canvas {
+      width: 100%;
+      height: 100%;
+      display: block;
+    }
+    </style>
+  </head>
+  <body>
+    <canvas id="c"></canvas>
+  </body>
+<script src="resources/threejs/r93/three.min.js"></script>
+<script src="resources/threejs/r93/js/controls/OrbitControls.js"></script>
+<script src="resources/threejs/r93/js/shaders/SSAOShader.js"></script>
+<script src="resources/threejs/r93/js/shaders/CopyShader.js"></script>
+<script src="resources/threejs/r93/js/postprocessing/EffectComposer.js"></script>
+<script src="resources/threejs/r93/js/postprocessing/RenderPass.js"></script>
+<script src="resources/threejs/r93/js/postprocessing/ShaderPass.js"></script>
+<script src="resources/threejs/r93/js/postprocessing/MaskPass.js"></script>
+<script src="resources/threejs/r93/js/postprocessing/SSAOPass.js"></script>
+<script src="resources/threejs/r93/js/loaders/GLTFLoader.js"></script>
+<script>
+'use strict';
+
+function main() {
+  const canvas = document.querySelector('#c');
+  const renderer = new THREE.WebGLRenderer({canvas: canvas});
+  const scene = new THREE.Scene();
+  scene.background = new THREE.Color().setHSL(0.6, 0, 1);
+
+  const aspect = 2;  // the canvas default
+  const fov = 60;
+  const zNear = 0.1;
+  const zFar = 5000;
+  const camera = new THREE.PerspectiveCamera(fov, aspect, zNear, zFar);
+  camera.position.x = -0.;
+  camera.position.y = 350;
+  camera.position.z = 40.;
+
+  const useFog = true;
+  const useOrbitCamera = false;
+  const showHelpers = false;
+  const camSpeed = 0.2;
+
+  if (useOrbitCamera) {
+    const controls = new THREE.OrbitControls(camera);
+    controls.target.set(0, 100.01, 0.2);
+    controls.update();
+  }
+
+  renderer.setClearColor(0xAACCFF);
+  renderer.gammaInput = true;
+  renderer.gammaOutput = true;
+  renderer.shadowMap.enabled = true;
+
+  const hemiLight = new THREE.HemisphereLight(0xffffff, 0xffffff, 0.6);
+  hemiLight.color.setHSL(0.6, 1, 0.6);
+  hemiLight.groundColor.setHSL(0.095, 1, 0.75);
+  hemiLight.position.set(0, 50, 0);
+  scene.add(hemiLight);
+
+  if (showHelpers) {
+    const hemiLightHelper = new THREE.HemisphereLightHelper(hemiLight, 10);
+    scene.add(hemiLightHelper);
+  }
+
+  const dirLight = new THREE.DirectionalLight(0xffffff, 1);
+  dirLight.color.setHSL(0.1, 1, 0.95);
+  dirLight.position.set(-300, 220, 245);
+  scene.add(dirLight);
+  dirLight.castShadow = true;
+  dirLight.shadow.mapSize.width = 2048;
+  dirLight.shadow.mapSize.height = 2048;
+  const d = 350;
+  dirLight.shadow.camera.left = -d;
+  dirLight.shadow.camera.right = d;
+  dirLight.shadow.camera.top = d;
+  dirLight.shadow.camera.bottom = -d;
+  dirLight.shadow.camera.near = 100;
+  dirLight.shadow.camera.far = 950;
+  dirLight.shadow.bias = -0.01;
+
+  if (showHelpers) {
+    const dirLightHeper = new THREE.DirectionalLightHelper(dirLight, 10);
+    scene.add(dirLightHeper);
+  }
+
+  const loader = new THREE.GLTFLoader();
+  const camRadius = 600;
+  const camHeight = 160;
+  const camTarget = [0, 30, 0];
+  const fogNear = 1350;
+  const fogFar = 1500;
+  loader.load('resources/models/mountain_landscape/scene.gltf', (gltf) => {
+    gltf.scene.traverse((child) => {
+      if ( child.isMesh ) {
+        child.castShadow = true;
+        child.receiveShadow = true;
+      }
+    });
+    scene.add(gltf.scene);
+  });
+
+  window.s = scene;
+
+  if (useFog) {
+    const vertexShader = `
+    varying vec3 vWorldPosition;
+    void main() {
+      vec4 worldPosition = modelMatrix * vec4( position, 1.0 );
+      vWorldPosition = worldPosition.xyz;
+      gl_Position = projectionMatrix * modelViewMatrix * vec4( position, 1.0 );
+    }
+    `;
+
+    const fragmentShader = `
+    uniform vec3 topColor;
+    uniform vec3 bottomColor;
+    uniform float offset;
+    uniform float exponent;
+    varying vec3 vWorldPosition;
+    void main() {
+      float h = normalize( vWorldPosition + offset ).y;
+      gl_FragColor = vec4( mix( bottomColor, topColor, max( pow( max( h , 0.0), exponent ), 0.0 ) ), 1.0 );
+    }
+    `;
+
+    const uniforms = {
+      topColor:    { value: new THREE.Color(0x88AABB) },
+      bottomColor: { value: new THREE.Color(0xEFCB7F) },
+      offset:      { value: 730 },
+      exponent:    { value: 0.3 },
+    };
+    uniforms.topColor.value.copy(hemiLight.color);
+    scene.fog = new THREE.Fog(scene.background, fogNear, fogFar);
+    scene.fog.color.copy(uniforms.bottomColor.value);
+    const skyGeo = new THREE.SphereBufferGeometry(4000, 32, 15);
+    const skyMat = new THREE.ShaderMaterial( { vertexShader: vertexShader, fragmentShader: fragmentShader, uniforms: uniforms, side: THREE.BackSide } );
+    const sky = new THREE.Mesh( skyGeo, skyMat );
+    scene.add(sky);
+  }
+
+  function resizeRendererToDisplaySize(renderer) {
+    const canvas = renderer.domElement;
+    const width = canvas.clientWidth;
+    const height = canvas.clientHeight;
+    if (width === canvas.width && height === canvas.height) {
+      return false;
+    }
+
+    renderer.setSize(width, height, false);
+    return true;
+  }
+
+  function render(time) {
+    time *= 0.001;
+    time += 80;
+
+    if (resizeRendererToDisplaySize(renderer)) {
+      camera.aspect = canvas.clientWidth / canvas.clientHeight;
+      camera.fov = fov / camera.aspect;
+      camera.updateProjectionMatrix();
+    }
+
+    if (!useOrbitCamera) {
+      const angle = Math.sin(time * camSpeed) + Math.PI * .75;
+      camera.position.set(Math.cos(angle) * camRadius, camHeight, Math.sin(angle) * camRadius);
+      camera.lookAt(...camTarget);
+    }
+
+    renderer.render(scene, camera);
+
+    requestAnimationFrame(render);
+  }
+
+  requestAnimationFrame(render);
+}
+
+main();
+</script>
+</html>
+

+ 15 - 0
threejs/lessons/index.md

@@ -0,0 +1,15 @@
+Title: Three.js Fundamentals
+
+Learn Three.js
+
+{{{include "threejs/lessons/toc.html"}}}
+
+
+<!--
+
+{{{table_of_contents}}}
+
+-->
+
+
+

+ 13 - 0
threejs/lessons/langinfo.hanson

@@ -0,0 +1,13 @@
+{
+  language: 'English',
+  langCode: 'en',   // if not specified will use folder
+  defaultExampleCaption: "click here to open in a separate window",
+  title: 'three.js fundamentals',
+  description: 'Learn Three.js',
+  link: 'http://threejsfundamentals.org/',
+  commentSectionHeader: '<div>Questions? <a href="http://stackoverflow.com/questions/tagged/three.js">Ask on stackoverflow</a>.</div>\n        <div>Issue/Bug? <a href="http://github.com/greggman/threejsfundamentals/issues">Create an issue on github</a>.</div>',
+  missing: "Sorry this article has not been translated yet. [Translations Welcome](https://github.com/greggman/threejsfundamentals)! 😄\n\n[Here's the original English article for now]({{{origLink}}}).",
+  toc: 'Table of Contents',
+}
+
+

BIN
threejs/lessons/resources/avatar-icon.png


BIN
threejs/lessons/resources/banner-00.jpg


BIN
threejs/lessons/resources/banner-00.png


+ 44 - 0
threejs/lessons/resources/index.css

@@ -0,0 +1,44 @@
+body {
+  margin: 0;
+  width: 100vw;
+}
+.lesson-main>* {
+    margin: 1em 0;
+}
+.background {
+    position: fixed;
+    width: 100vw;
+    height: 100vh;
+    border: none;
+    top: 0;
+    z-index: -1000;
+}
+.container {
+    margin: 2em auto;
+    padding: 15px 50px;
+    border-radius: 25px;
+    background-color: rgba(255, 255, 255, 0.9);
+    max-width: 700px;
+    width: 80%;
+}
+#language {
+    margin-top: -2em;
+    width: auto;
+    font-size: large;
+}
+.rss {
+   float:right;
+   width:46px;
+   height:46px;
+}
+.rss img {
+   width: 46px;
+   height: 46px;
+}
+
+
+@media (max-width: 600px) {
+    .container {
+        padding: 15px 20px;
+    }
+}

+ 394 - 0
threejs/lessons/resources/lesson.css

@@ -0,0 +1,394 @@
+body {
+  margin: 0;
+  font-family: Georgia, serif;
+  font-size: 19px;
+  line-height: 150%;
+}
+
+p + p {
+  margin-top: 1.5em;
+}
+li > p {
+  width: calc(100% - 2em);
+}
+
+table {
+    margin-top: 1em;
+    margin-bottom: 1em;
+}
+pre.prettyprint {
+    margin-top: 2em !important;
+    margin-bottom: 2em !important;
+}
+pre.prettyprint li {
+    white-space: pre;
+}
+
+.threejs_navbar>div,
+.lesson-title,
+.lesson-comments,
+.lesson-comment-sep,
+.lesson-main>* {
+    margin: 0 auto 1em;
+    max-width: 700px;
+    width: calc(100% - 40px);
+}
+.lesson-main>.threejs_example_container {
+    max-width: 90%;
+}
+
+.threejs_example {
+    width: 100%;
+    height: 500px;
+}
+.threejs_header {
+    background-image: url(/threejs/lessons/resources/banner-00.jpg);
+    background-size: cover;
+    background-position: center, center;
+    padding: 1em;
+    text-align: center;
+}
+.threejs_header h1 {
+    font-size: 5vw;
+    margin: 0;
+    background-image: url(/threejs/lessons/resources/logo.png);
+    background-size: contain;
+    background-position: center center;
+    background-repeat: no-repeat;
+
+    font-weight: bold;
+    color: black;
+    /*
+    text-shadow: 0px 0px 15px #fff,
+                 0px 0px 5px #fff,
+                 0px 0px 10px #fff;
+    */
+}
+.threejs_header a {
+    color: rgba(0, 0, 0, 0);
+    text-decoration: none;
+}
+.threejs_navbar {
+    background: black;
+    color: white;
+}
+.threejs_navbar a {
+    color: white;
+}
+.threejs_navbar>div {
+    margin: 0 auto;
+    display: flex;
+    justify-content: space-between;
+    align-items: center;
+}
+.threejs_navbar>div>* {
+    display: block;
+    margin: .25em 0;
+}
+.threejs_navbar select {
+    background: #444;
+    border: none;
+    font-size: large;
+    color: white;
+}
+.home-lang select {
+    font-size: large;
+}
+
+.fullscreen {
+    position: fixed !important;
+    left: 0;
+    top: 0;
+    width: 100vw !important;
+    height: 100vh !important;
+    z-index: 100;
+}
+.lesson-main>blockquote {
+    background-color: #DEF;
+    padding: 1em;
+}
+.lesson-title {
+    margin-top: 3em;
+    margin-bottom: 2em;
+}
+.lesson-main {
+  gbackground-color: #ffe;
+}
+.lesson-sidebar {
+    font-size: small;
+    columns: 220px;
+    padding: 1em;
+    column-rule: dashed 1px #CCC;
+    background: #eee;
+    margin-bottom: 1em;
+}
+.lesson-sidebar>ul>li {
+    line-height: 1.3em;
+}
+.lesson-sidebar ul {
+    line-height: 1.3em;
+    margin-bottom: 1em;
+}
+.lesson-sidebar ul {
+    list-style-type: none;
+    padding-left: 1em;
+    text-indent: -1em;
+}
+h1, h2, h3, h4 {
+  font-family: sans-serif;
+  line-height: 1.2;
+}
+h3 {
+  font-size: medium;
+}
+code {
+    color: black;
+    font-family: monospace;
+    background-color: #ddd;
+    padding: 0.1em 0.2em 0.1em 0.2em;
+    border-radius: 0.5em;
+    white-space: nowrap;
+}
+
+.threejs_table_div_center {
+  text-align: center;
+}
+
+.threejs_table_center {
+  margin-left: auto;
+  margin-right: auto;
+}
+
+.threejs_center {
+  margin-left: auto;
+  margin-top: 1em;
+  margin-bottom: 1em;
+  margin-right: auto;
+  display: block;
+  text-align: center;
+  max-width: 95%;
+}
+
+.threejs_image>img {
+    width: 100%;
+}
+.threejs_math {
+  margin-left: auto;
+  margin-right: auto;
+  display: inline-block;
+  text-align: left;
+}
+
+.threejs_math_center {
+  display: block;
+  text-align: center;
+}
+
+.hcenter {
+  display: -webkit-box;
+  -webkit-box-orient: horizontal;
+  -webkit-box-pack: center;
+  -webkit-box-align: center;
+
+  display: -moz-box;
+  -moz-box-orient: horizontal;
+  -moz-box-pack: center;
+  -moz-box-align: center;
+
+  display: box;
+  box-orient: horizontal;
+  box-pack: center;
+  box-align: center;
+}
+
+table.vertex_table {
+  border: 1px solid black;
+  border-collapse: collapse;
+  font-family: monospace;
+  font-size: small;
+}
+
+table.vertex_table th {
+  background-color: #88ccff;
+  padding-right: 1em;
+  padding-left: 1em;
+}
+
+table.vertex_table td {
+  border: 1px solid black;
+  text-align: right;
+  padding-right: 1em;
+  padding-left: 1em;
+}
+
+iframe {
+    display: block;
+}
+
+iframe.body {
+  height: 100vh;
+}
+iframe.threejs_example {
+  border: none;
+  margin-left: auto;
+  margin-right: auto;
+  display: block;
+  border: 1px solid black;
+}
+iframe.noborder {
+  border: none !important;
+}
+
+iframe.external_diagram {
+  border: none;
+  margin-left: auto;
+  margin-right: auto;
+  display: block;
+}
+
+div.threejs_bottombar {
+  border: 1px solid #000;
+  background-color: #def;
+  padding: 1em;
+  width: calc(100% - 80px);
+}
+div.threejs_bottombar>h3 {
+  font-size: x-large;
+  font-weight: bold;
+  margin-bottom: 1em;
+}
+div.threejs_bottombar code {
+    background-color: #ccc;
+}
+
+/* --- Prettify --- */
+pre.prettyprint .nocode { background-color: none; color: #FFF }
+pre.prettyprint .str { color: #b9ca4a } /* string          */
+pre.prettyprint .kwd { color: #c397d8 } /* keyword         */
+pre.prettyprint .com { color: #f3efb2 } /* comment         */
+pre.prettyprint .typ { color: #7aa6da } /* type            */
+pre.prettyprint .lit { color: #45e7a6 } /* literal         */
+pre.prettyprint .pun { color: #7ecce0 } /* punctuation     */
+pre.prettyprint .pln { color: #eaeaea } /* plaintext       */
+pre.prettyprint .tag { color: #d54e53 } /* html/xml tag    */
+pre.prettyprint .atn { color: #e78c45 } /* attribute name  */
+pre.prettyprint .atv { color: #70c0b1 } /* attribute value */
+pre.prettyprint .dec { color: #e78c45 } /* decimal         */
+pre.prettyprint .var { color: #d54e53 } /* variable name   */
+pre.prettyprint .fun { color: #7aa6da } /* function name   */
+
+pre.prettyprint ul.modifiedlines {
+    list-style-type: none;
+    padding-left: 0;
+}
+pre.prettyprint ul.modifiedlines li.linemodified {
+    list-style-type: none;
+    background-color: #324840;
+}
+pre.prettyprint ul.modifiedlines li.linedeleted {
+    list-style-type: none;
+    background-color: #4c1414;
+    text-decoration: line-through;
+}
+
+pre.prettyprint ul.modifiedlines li.lineadded {
+    list-style-type: none;
+    background-color: #3f4463;
+}
+
+
+pre.prettyprint, code.prettyprint {
+    color: #FFF;
+    background: #222;
+    border: 1px solid #000;
+    box-shadow: 10px 10px 0px #ccc;
+    font-size: 9pt;
+    font-family: "Lucida Console", Monaco, monospace;
+    margin: auto;
+    padding: 1em;
+    text-align: left;           /* override justify on body */
+    /* this was disabled until 2016-08-26 but I don't know why */
+    overflow: auto;             /* allow scroll bar in case of long lines - goes together with white-space: nowrap! */
+    white-space: pre;        /* was nowrap, prevent line wrapping */
+    line-height: 1.5em;
+    width: calc(100% - 80px);
+}
+
+@media print {
+    pre.prettyprint .str, code.prettyprint .str{color:#060}
+    pre.prettyprint .kwd, code.prettyprint .kwd{color:#006;font-weight:bold}
+    pre.prettyprint .com, code.prettyprint .com{color:#600;font-style:italic}
+    pre.prettyprint .typ, code.prettyprint .typ{color:#404;font-weight:bold}
+    pre.prettyprint .lit, code.prettyprint .lit{color:#044}
+    pre.prettyprint .pun, code.prettyprint .pun{color:#440}
+    pre.prettyprint .pln, code.prettyprint .pln{color:#000}
+    pre.prettyprint .tag, code.prettyprint .tag{color:#006;font-weight:bold}
+    pre.prettyprint .atn, code.prettyprint .atn{color:#404}
+    pre.prettyprint .atv, code.prettyprint .atv{color:#060}
+    pre.prettyprint, code.prettyprint {
+        color: #000;
+        background: #EEE;
+        font-size: 8pt;
+        font-family: "Lucida Console", Monaco, monospace;
+        width: 95%;
+        margin: auto;
+        padding: 1em;
+        text-align: left;           /* override justify on body */
+        overflow: visible;
+        white-space: pre;        /* was nowrap, prevent line wrapping */
+        line-height: 1.5;
+    }
+
+    pre.prettyprint ul.modifiedlines li.linemodified {
+        list-style-type: none;
+        background-color: #DDD;
+    }
+    pre.prettyprint ul.modifiedlines li.linedeleted {
+        list-style-type: none;
+        background-color: #CCC;
+        text-decoration: line-through;
+    }
+
+    pre.prettyprint ul.modifiedlines li.lineadded {
+        list-style-type: none;
+        background-color: #EEE;
+    }
+
+    body {
+        margin: 10mm;
+    }
+    .doubleSpace p {
+        line-height: 2.5;
+        font-size: x-large;
+    }
+    .doubleSpace pre.prettyprint {
+        font-size: 14pt;
+    }
+    .threejs_navbar,
+    .lesson-comment-sep,
+    .lesson-sidebar,
+    .lesson-comments {
+        display: none;
+    }
+}
+
+@media (max-height: 700px) {
+    .threejs_example {
+        height: 400px;
+    }
+}
+
+@media (max-width: 720px) {
+    body {
+        font-size: 16px;
+    }
+    h1 {
+        font-size: 24px;
+    }
+    iframe {
+        max-width: 95% !important;
+    }
+}
+
+
+

+ 76 - 0
threejs/lessons/resources/lesson.js

@@ -0,0 +1,76 @@
+<!-- Licensed under a BSD license. See license.html for license -->
+(function($){
+var log = function(msg) {
+  return;
+  if (window.dump) {
+    dump(msg + "\n");
+  }
+  if (window.console && window.console.log) {
+    console.log(msg);
+  }
+};
+
+function getQueryParams() {
+  var params = {};
+  if (window.location.search) {
+    window.location.search.substring(1).split("&").forEach(function(pair) {
+      var keyValue = pair.split("=").map(function (kv) {
+        return decodeURIComponent(kv);
+      });
+      params[keyValue[0]] = keyValue[1];
+    });
+  }
+  return params;
+}
+
+$(document).ready(function($){
+  var g_imgs = { };
+  var linkImgs = function(bigHref) {
+    return function() {
+      var src = this.src;
+      var a = document.createElement('a');
+      a.href = bigHref;
+      a.title = this.alt;
+      a.className = this.className;
+      a.setAttribute('align', this.align);
+      this.setAttribute('align', '');
+      this.className = '';
+      this.style.border = "0px";
+      return a;
+    };
+  };
+  var linkSmallImgs = function(ext) {
+    return function() {
+      var src = this.src;
+      return linkImgs(src.substr(0, src.length - 7) + ext);
+    };
+  };
+  var linkBigImgs = function() {
+    var src = $(this).attr("big");
+    return linkImgs(src);
+  };
+  $('img[big$=".jpg"]').wrap(linkBigImgs);
+  $('img[src$="-sm.jpg"]').wrap(linkSmallImgs(".jpg"));
+  $('img[src$="-sm.gif"]').wrap(linkSmallImgs(".gif"));
+  $('img[src$="-sm.png"]').wrap(linkSmallImgs(".png"));
+  $('pre>code')
+     .unwrap()
+     .replaceWith(function() {
+       return $('<pre class="prettyprint showlinemods">' + this.innerHTML + '</pre>')
+     });
+  if (window.prettyPrint) {
+    window.prettyPrint();
+  }
+
+  var params = getQueryParams();
+  if (params.doubleSpace || params.doublespace) {
+    document.body.className = document.body.className + " doubleSpace";
+  }
+
+  $(".language").on('change', function() {
+    window.location.href = this.value;
+  });
+
+});
+}(jQuery));
+

BIN
threejs/lessons/resources/logo.png


+ 1713 - 0
threejs/lessons/resources/prettify.js

@@ -0,0 +1,1713 @@
+// Copyright (C) 2006 Google Inc.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//      http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+
+/**
+ * @fileoverview
+ * some functions for browser-side pretty printing of code contained in html.
+ *
+ * <p>
+ * For a fairly comprehensive set of languages see the
+ * <a href="http://google-code-prettify.googlecode.com/svn/trunk/README.html#langs">README</a>
+ * file that came with this source.  At a minimum, the lexer should work on a
+ * number of languages including C and friends, Java, Python, Bash, SQL, HTML,
+ * XML, CSS, Javascript, and Makefiles.  It works passably on Ruby, PHP and Awk
+ * and a subset of Perl, but, because of commenting conventions, doesn't work on
+ * Smalltalk, Lisp-like, or CAML-like languages without an explicit lang class.
+ * <p>
+ * Usage: <ol>
+ * <li> include this source file in an html page via
+ *   {@code <script type="text/javascript" src="/path/to/prettify.js"></script>}
+ * <li> define style rules.  See the example page for examples.
+ * <li> mark the {@code <pre>} and {@code <code>} tags in your source with
+ *    {@code class=prettyprint.}
+ *    You can also use the (html deprecated) {@code <xmp>} tag, but the pretty
+ *    printer needs to do more substantial DOM manipulations to support that, so
+ *    some css styles may not be preserved.
+ * </ol>
+ * That's it.  I wanted to keep the API as simple as possible, so there's no
+ * need to specify which language the code is in, but if you wish, you can add
+ * another class to the {@code <pre>} or {@code <code>} element to specify the
+ * language, as in {@code <pre class="prettyprint lang-java">}.  Any class that
+ * starts with "lang-" followed by a file extension, specifies the file type.
+ * See the "lang-*.js" files in this directory for code that implements
+ * per-language file handlers.
+ * <p>
+ * Change log:<br>
+ * cbeust, 2006/08/22
+ * <blockquote>
+ *   Java annotations (start with "@") are now captured as literals ("lit")
+ * </blockquote>
+ * @requires console
+ */
+
+// JSLint declarations
+/*global console, document, navigator, setTimeout, window, define */
+
+/** @define {boolean} */
+var IN_GLOBAL_SCOPE = true;
+
+/**
+ * Split {@code prettyPrint} into multiple timeouts so as not to interfere with
+ * UI events.
+ * If set to {@code false}, {@code prettyPrint()} is synchronous.
+ */
+window['PR_SHOULD_USE_CONTINUATION'] = true;
+
+/**
+ * Pretty print a chunk of code.
+ * @param {string} sourceCodeHtml The HTML to pretty print.
+ * @param {string} opt_langExtension The language name to use.
+ *     Typically, a filename extension like 'cpp' or 'java'.
+ * @param {number|boolean} opt_numberLines True to number lines,
+ *     or the 1-indexed number of the first line in sourceCodeHtml.
+ * @return {string} code as html, but prettier
+ */
+var prettyPrintOne;
+/**
+ * Find all the {@code <pre>} and {@code <code>} tags in the DOM with
+ * {@code class=prettyprint} and prettify them.
+ *
+ * @param {Function} opt_whenDone called when prettifying is done.
+ * @param {HTMLElement|HTMLDocument} opt_root an element or document
+ *   containing all the elements to pretty print.
+ *   Defaults to {@code document.body}.
+ */
+var prettyPrint;
+
+
+(function () {
+  var win = window;
+  // Keyword lists for various languages.
+  // We use things that coerce to strings to make them compact when minified
+  // and to defeat aggressive optimizers that fold large string constants.
+  var FLOW_CONTROL_KEYWORDS = ["break,continue,do,else,for,if,return,while"];
+  var C_KEYWORDS = [FLOW_CONTROL_KEYWORDS,"auto,case,char,const,default," + 
+      "double,enum,extern,float,goto,inline,int,long,register,short,signed," +
+      "sizeof,static,struct,switch,typedef,union,unsigned,void,volatile"];
+  var COMMON_KEYWORDS = [C_KEYWORDS,"catch,class,delete,false,import," +
+      "new,operator,private,protected,public,this,throw,true,try,typeof"];
+  var CPP_KEYWORDS = [COMMON_KEYWORDS,"alignof,align_union,asm,axiom,bool," +
+      "concept,concept_map,const_cast,constexpr,decltype,delegate," +
+      "dynamic_cast,explicit,export,friend,generic,late_check," +
+      "mutable,namespace,nullptr,property,reinterpret_cast,static_assert," +
+      "static_cast,template,typeid,typename,using,virtual,where"];
+  var JAVA_KEYWORDS = [COMMON_KEYWORDS,
+      "abstract,assert,boolean,byte,extends,final,finally,implements,import," +
+      "instanceof,interface,null,native,package,strictfp,super,synchronized," +
+      "throws,transient"];
+  var CSHARP_KEYWORDS = [COMMON_KEYWORDS,
+      "abstract,as,base,bool,by,byte,checked,decimal,delegate,descending," +
+      "dynamic,event,finally,fixed,foreach,from,group,implicit,in,interface," +
+      "internal,into,is,let,lock,null,object,out,override,orderby,params," +
+      "partial,readonly,ref,sbyte,sealed,stackalloc,string,select,uint,ulong," +
+      "unchecked,unsafe,ushort,var,virtual,where"];
+  var COFFEE_KEYWORDS = "all,and,by,catch,class,else,extends,false,finally," +
+      "for,if,in,is,isnt,loop,new,no,not,null,of,off,on,or,return,super,then," +
+      "throw,true,try,unless,until,when,while,yes";
+  var JSCRIPT_KEYWORDS = [COMMON_KEYWORDS,
+      "debugger,eval,export,function,get,null,set,undefined,var,with," +
+      "Infinity,NaN"];
+  var PERL_KEYWORDS = "caller,delete,die,do,dump,elsif,eval,exit,foreach,for," +
+      "goto,if,import,last,local,my,next,no,our,print,package,redo,require," +
+      "sub,undef,unless,until,use,wantarray,while,BEGIN,END";
+  var PYTHON_KEYWORDS = [FLOW_CONTROL_KEYWORDS, "and,as,assert,class,def,del," +
+      "elif,except,exec,finally,from,global,import,in,is,lambda," +
+      "nonlocal,not,or,pass,print,raise,try,with,yield," +
+      "False,True,None"];
+  var RUBY_KEYWORDS = [FLOW_CONTROL_KEYWORDS, "alias,and,begin,case,class," +
+      "def,defined,elsif,end,ensure,false,in,module,next,nil,not,or,redo," +
+      "rescue,retry,self,super,then,true,undef,unless,until,when,yield," +
+      "BEGIN,END"];
+   var RUST_KEYWORDS = [FLOW_CONTROL_KEYWORDS, "as,assert,const,copy,drop," +
+      "enum,extern,fail,false,fn,impl,let,log,loop,match,mod,move,mut,priv," +
+      "pub,pure,ref,self,static,struct,true,trait,type,unsafe,use"];
+  var SH_KEYWORDS = [FLOW_CONTROL_KEYWORDS, "case,done,elif,esac,eval,fi," +
+      "function,in,local,set,then,until"];
+  var ALL_KEYWORDS = [
+      CPP_KEYWORDS, CSHARP_KEYWORDS, JSCRIPT_KEYWORDS, PERL_KEYWORDS,
+      PYTHON_KEYWORDS, RUBY_KEYWORDS, SH_KEYWORDS];
+  var C_TYPES = /^(DIR|FILE|vector|(de|priority_)?queue|list|stack|(const_)?iterator|(multi)?(set|map)|bitset|u?(int|float)\d*)\b/;
+
+  // token style names.  correspond to css classes
+  /**
+   * token style for a string literal
+   * @const
+   */
+  var PR_STRING = 'str';
+  /**
+   * token style for a keyword
+   * @const
+   */
+  var PR_KEYWORD = 'kwd';
+  /**
+   * token style for a comment
+   * @const
+   */
+  var PR_COMMENT = 'com';
+  /**
+   * token style for a type
+   * @const
+   */
+  var PR_TYPE = 'typ';
+  /**
+   * token style for a literal value.  e.g. 1, null, true.
+   * @const
+   */
+  var PR_LITERAL = 'lit';
+  /**
+   * token style for a punctuation string.
+   * @const
+   */
+  var PR_PUNCTUATION = 'pun';
+  /**
+   * token style for plain text.
+   * @const
+   */
+  var PR_PLAIN = 'pln';
+
+  /**
+   * token style for an sgml tag.
+   * @const
+   */
+  var PR_TAG = 'tag';
+  /**
+   * token style for a markup declaration such as a DOCTYPE.
+   * @const
+   */
+  var PR_DECLARATION = 'dec';
+  /**
+   * token style for embedded source.
+   * @const
+   */
+  var PR_SOURCE = 'src';
+  /**
+   * token style for an sgml attribute name.
+   * @const
+   */
+  var PR_ATTRIB_NAME = 'atn';
+  /**
+   * token style for an sgml attribute value.
+   * @const
+   */
+  var PR_ATTRIB_VALUE = 'atv';
+
+  /**
+   * A class that indicates a section of markup that is not code, e.g. to allow
+   * embedding of line numbers within code listings.
+   * @const
+   */
+  var PR_NOCODE = 'nocode';
+
+  
+  
+  /**
+   * A set of tokens that can precede a regular expression literal in
+   * javascript
+   * http://web.archive.org/web/20070717142515/http://www.mozilla.org/js/language/js20/rationale/syntax.html
+   * has the full list, but I've removed ones that might be problematic when
+   * seen in languages that don't support regular expression literals.
+   *
+   * <p>Specifically, I've removed any keywords that can't precede a regexp
+   * literal in a syntactically legal javascript program, and I've removed the
+   * "in" keyword since it's not a keyword in many languages, and might be used
+   * as a count of inches.
+   *
+   * <p>The link above does not accurately describe EcmaScript rules since
+   * it fails to distinguish between (a=++/b/i) and (a++/b/i) but it works
+   * very well in practice.
+   *
+   * @private
+   * @const
+   */
+  var REGEXP_PRECEDER_PATTERN = '(?:^^\\.?|[+-]|[!=]=?=?|\\#|%=?|&&?=?|\\(|\\*=?|[+\\-]=|->|\\/=?|::?|<<?=?|>>?>?=?|,|;|\\?|@|\\[|~|{|\\^\\^?=?|\\|\\|?=?|break|case|continue|delete|do|else|finally|instanceof|return|throw|try|typeof)\\s*';
+  
+  // CAVEAT: this does not properly handle the case where a regular
+  // expression immediately follows another since a regular expression may
+  // have flags for case-sensitivity and the like.  Having regexp tokens
+  // adjacent is not valid in any language I'm aware of, so I'm punting.
+  // TODO: maybe style special characters inside a regexp as punctuation.
+
+  /**
+   * Given a group of {@link RegExp}s, returns a {@code RegExp} that globally
+   * matches the union of the sets of strings matched by the input RegExp.
+   * Since it matches globally, if the input strings have a start-of-input
+   * anchor (/^.../), it is ignored for the purposes of unioning.
+   * @param {Array.<RegExp>} regexs non multiline, non-global regexs.
+   * @return {RegExp} a global regex.
+   */
+  function combinePrefixPatterns(regexs) {
+    var capturedGroupIndex = 0;
+  
+    var needToFoldCase = false;
+    var ignoreCase = false;
+    for (var i = 0, n = regexs.length; i < n; ++i) {
+      var regex = regexs[i];
+      if (regex.ignoreCase) {
+        ignoreCase = true;
+      } else if (/[a-z]/i.test(regex.source.replace(
+                     /\\u[0-9a-f]{4}|\\x[0-9a-f]{2}|\\[^ux]/gi, ''))) {
+        needToFoldCase = true;
+        ignoreCase = false;
+        break;
+      }
+    }
+  
+    var escapeCharToCodeUnit = {
+      'b': 8,
+      't': 9,
+      'n': 0xa,
+      'v': 0xb,
+      'f': 0xc,
+      'r': 0xd
+    };
+  
+    function decodeEscape(charsetPart) {
+      var cc0 = charsetPart.charCodeAt(0);
+      if (cc0 !== 92 /* \\ */) {
+        return cc0;
+      }
+      var c1 = charsetPart.charAt(1);
+      cc0 = escapeCharToCodeUnit[c1];
+      if (cc0) {
+        return cc0;
+      } else if ('0' <= c1 && c1 <= '7') {
+        return parseInt(charsetPart.substring(1), 8);
+      } else if (c1 === 'u' || c1 === 'x') {
+        return parseInt(charsetPart.substring(2), 16);
+      } else {
+        return charsetPart.charCodeAt(1);
+      }
+    }
+  
+    function encodeEscape(charCode) {
+      if (charCode < 0x20) {
+        return (charCode < 0x10 ? '\\x0' : '\\x') + charCode.toString(16);
+      }
+      var ch = String.fromCharCode(charCode);
+      return (ch === '\\' || ch === '-' || ch === ']' || ch === '^')
+          ? "\\" + ch : ch;
+    }
+  
+    function caseFoldCharset(charSet) {
+      var charsetParts = charSet.substring(1, charSet.length - 1).match(
+          new RegExp(
+              '\\\\u[0-9A-Fa-f]{4}'
+              + '|\\\\x[0-9A-Fa-f]{2}'
+              + '|\\\\[0-3][0-7]{0,2}'
+              + '|\\\\[0-7]{1,2}'
+              + '|\\\\[\\s\\S]'
+              + '|-'
+              + '|[^-\\\\]',
+              'g'));
+      var ranges = [];
+      var inverse = charsetParts[0] === '^';
+  
+      var out = ['['];
+      if (inverse) { out.push('^'); }
+  
+      for (var i = inverse ? 1 : 0, n = charsetParts.length; i < n; ++i) {
+        var p = charsetParts[i];
+        if (/\\[bdsw]/i.test(p)) {  // Don't muck with named groups.
+          out.push(p);
+        } else {
+          var start = decodeEscape(p);
+          var end;
+          if (i + 2 < n && '-' === charsetParts[i + 1]) {
+            end = decodeEscape(charsetParts[i + 2]);
+            i += 2;
+          } else {
+            end = start;
+          }
+          ranges.push([start, end]);
+          // If the range might intersect letters, then expand it.
+          // This case handling is too simplistic.
+          // It does not deal with non-latin case folding.
+          // It works for latin source code identifiers though.
+          if (!(end < 65 || start > 122)) {
+            if (!(end < 65 || start > 90)) {
+              ranges.push([Math.max(65, start) | 32, Math.min(end, 90) | 32]);
+            }
+            if (!(end < 97 || start > 122)) {
+              ranges.push([Math.max(97, start) & ~32, Math.min(end, 122) & ~32]);
+            }
+          }
+        }
+      }
+  
+      // [[1, 10], [3, 4], [8, 12], [14, 14], [16, 16], [17, 17]]
+      // -> [[1, 12], [14, 14], [16, 17]]
+      ranges.sort(function (a, b) { return (a[0] - b[0]) || (b[1]  - a[1]); });
+      var consolidatedRanges = [];
+      var lastRange = [];
+      for (var i = 0; i < ranges.length; ++i) {
+        var range = ranges[i];
+        if (range[0] <= lastRange[1] + 1) {
+          lastRange[1] = Math.max(lastRange[1], range[1]);
+        } else {
+          consolidatedRanges.push(lastRange = range);
+        }
+      }
+  
+      for (var i = 0; i < consolidatedRanges.length; ++i) {
+        var range = consolidatedRanges[i];
+        out.push(encodeEscape(range[0]));
+        if (range[1] > range[0]) {
+          if (range[1] + 1 > range[0]) { out.push('-'); }
+          out.push(encodeEscape(range[1]));
+        }
+      }
+      out.push(']');
+      return out.join('');
+    }
+  
+    function allowAnywhereFoldCaseAndRenumberGroups(regex) {
+      // Split into character sets, escape sequences, punctuation strings
+      // like ('(', '(?:', ')', '^'), and runs of characters that do not
+      // include any of the above.
+      var parts = regex.source.match(
+          new RegExp(
+              '(?:'
+              + '\\[(?:[^\\x5C\\x5D]|\\\\[\\s\\S])*\\]'  // a character set
+              + '|\\\\u[A-Fa-f0-9]{4}'  // a unicode escape
+              + '|\\\\x[A-Fa-f0-9]{2}'  // a hex escape
+              + '|\\\\[0-9]+'  // a back-reference or octal escape
+              + '|\\\\[^ux0-9]'  // other escape sequence
+              + '|\\(\\?[:!=]'  // start of a non-capturing group
+              + '|[\\(\\)\\^]'  // start/end of a group, or line start
+              + '|[^\\x5B\\x5C\\(\\)\\^]+'  // run of other characters
+              + ')',
+              'g'));
+      var n = parts.length;
+  
+      // Maps captured group numbers to the number they will occupy in
+      // the output or to -1 if that has not been determined, or to
+      // undefined if they need not be capturing in the output.
+      var capturedGroups = [];
+  
+      // Walk over and identify back references to build the capturedGroups
+      // mapping.
+      for (var i = 0, groupIndex = 0; i < n; ++i) {
+        var p = parts[i];
+        if (p === '(') {
+          // groups are 1-indexed, so max group index is count of '('
+          ++groupIndex;
+        } else if ('\\' === p.charAt(0)) {
+          var decimalValue = +p.substring(1);
+          if (decimalValue) {
+            if (decimalValue <= groupIndex) {
+              capturedGroups[decimalValue] = -1;
+            } else {
+              // Replace with an unambiguous escape sequence so that
+              // an octal escape sequence does not turn into a backreference
+              // to a capturing group from an earlier regex.
+              parts[i] = encodeEscape(decimalValue);
+            }
+          }
+        }
+      }
+  
+      // Renumber groups and reduce capturing groups to non-capturing groups
+      // where possible.
+      for (var i = 1; i < capturedGroups.length; ++i) {
+        if (-1 === capturedGroups[i]) {
+          capturedGroups[i] = ++capturedGroupIndex;
+        }
+      }
+      for (var i = 0, groupIndex = 0; i < n; ++i) {
+        var p = parts[i];
+        if (p === '(') {
+          ++groupIndex;
+          if (!capturedGroups[groupIndex]) {
+            parts[i] = '(?:';
+          }
+        } else if ('\\' === p.charAt(0)) {
+          var decimalValue = +p.substring(1);
+          if (decimalValue && decimalValue <= groupIndex) {
+            parts[i] = '\\' + capturedGroups[decimalValue];
+          }
+        }
+      }
+  
+      // Remove any prefix anchors so that the output will match anywhere.
+      // ^^ really does mean an anchored match though.
+      for (var i = 0; i < n; ++i) {
+        if ('^' === parts[i] && '^' !== parts[i + 1]) { parts[i] = ''; }
+      }
+  
+      // Expand letters to groups to handle mixing of case-sensitive and
+      // case-insensitive patterns if necessary.
+      if (regex.ignoreCase && needToFoldCase) {
+        for (var i = 0; i < n; ++i) {
+          var p = parts[i];
+          var ch0 = p.charAt(0);
+          if (p.length >= 2 && ch0 === '[') {
+            parts[i] = caseFoldCharset(p);
+          } else if (ch0 !== '\\') {
+            // TODO: handle letters in numeric escapes.
+            parts[i] = p.replace(
+                /[a-zA-Z]/g,
+                function (ch) {
+                  var cc = ch.charCodeAt(0);
+                  return '[' + String.fromCharCode(cc & ~32, cc | 32) + ']';
+                });
+          }
+        }
+      }
+  
+      return parts.join('');
+    }
+  
+    var rewritten = [];
+    for (var i = 0, n = regexs.length; i < n; ++i) {
+      var regex = regexs[i];
+      if (regex.global || regex.multiline) { throw new Error('' + regex); }
+      rewritten.push(
+          '(?:' + allowAnywhereFoldCaseAndRenumberGroups(regex) + ')');
+    }
+  
+    return new RegExp(rewritten.join('|'), ignoreCase ? 'gi' : 'g');
+  }
+
+  /**
+   * Split markup into a string of source code and an array mapping ranges in
+   * that string to the text nodes in which they appear.
+   *
+   * <p>
+   * The HTML DOM structure:</p>
+   * <pre>
+   * (Element   "p"
+   *   (Element "b"
+   *     (Text  "print "))       ; #1
+   *   (Text    "'Hello '")      ; #2
+   *   (Element "br")            ; #3
+   *   (Text    "  + 'World';")) ; #4
+   * </pre>
+   * <p>
+   * corresponds to the HTML
+   * {@code <p><b>print </b>'Hello '<br>  + 'World';</p>}.</p>
+   *
+   * <p>
+   * It will produce the output:</p>
+   * <pre>
+   * {
+   *   sourceCode: "print 'Hello '\n  + 'World';",
+   *   //                     1          2
+   *   //           012345678901234 5678901234567
+   *   spans: [0, #1, 6, #2, 14, #3, 15, #4]
+   * }
+   * </pre>
+   * <p>
+   * where #1 is a reference to the {@code "print "} text node above, and so
+   * on for the other text nodes.
+   * </p>
+   *
+   * <p>
+   * The {@code} spans array is an array of pairs.  Even elements are the start
+   * indices of substrings, and odd elements are the text nodes (or BR elements)
+   * that contain the text for those substrings.
+   * Substrings continue until the next index or the end of the source.
+   * </p>
+   *
+   * @param {Node} node an HTML DOM subtree containing source-code.
+   * @param {boolean} isPreformatted true if white-space in text nodes should
+   *    be considered significant.
+   * @return {Object} source code and the text nodes in which they occur.
+   */
+  function extractSourceSpans(node, isPreformatted) {
+    var nocode = /(?:^|\s)nocode(?:\s|$)/;
+  
+    var chunks = [];
+    var length = 0;
+    var spans = [];
+    var k = 0;
+  
+    function walk(node) {
+      var type = node.nodeType;
+      if (type == 1) {  // Element
+        if (nocode.test(node.className)) { return; }
+        for (var child = node.firstChild; child; child = child.nextSibling) {
+          walk(child);
+        }
+        var nodeName = node.nodeName.toLowerCase();
+        if ('br' === nodeName || 'li' === nodeName) {
+          chunks[k] = '\n';
+          spans[k << 1] = length++;
+          spans[(k++ << 1) | 1] = node;
+        }
+      } else if (type == 3 || type == 4) {  // Text
+        var text = node.nodeValue;
+        if (text.length) {
+          if (!isPreformatted) {
+            text = text.replace(/[ \t\r\n]+/g, ' ');
+          } else {
+            text = text.replace(/\r\n?/g, '\n');  // Normalize newlines.
+          }
+          // TODO: handle tabs here?
+          chunks[k] = text;
+          spans[k << 1] = length;
+          length += text.length;
+          spans[(k++ << 1) | 1] = node;
+        }
+      }
+    }
+  
+    walk(node);
+  
+    return {
+      sourceCode: chunks.join('').replace(/\n$/, ''),
+      spans: spans
+    };
+  }
+
+  /**
+   * Apply the given language handler to sourceCode and add the resulting
+   * decorations to out.
+   * @param {number} basePos the index of sourceCode within the chunk of source
+   *    whose decorations are already present on out.
+   */
+  function appendDecorations(basePos, sourceCode, langHandler, out) {
+    if (!sourceCode) { return; }
+    var job = {
+      sourceCode: sourceCode,
+      basePos: basePos
+    };
+    langHandler(job);
+    out.push.apply(out, job.decorations);
+  }
+
+  var notWs = /\S/;
+
+  /**
+   * Given an element, if it contains only one child element and any text nodes
+   * it contains contain only space characters, return the sole child element.
+   * Otherwise returns undefined.
+   * <p>
+   * This is meant to return the CODE element in {@code <pre><code ...>} when
+   * there is a single child element that contains all the non-space textual
+   * content, but not to return anything where there are multiple child elements
+   * as in {@code <pre><code>...</code><code>...</code></pre>} or when there
+   * is textual content.
+   */
+  function childContentWrapper(element) {
+    var wrapper = undefined;
+    for (var c = element.firstChild; c; c = c.nextSibling) {
+      var type = c.nodeType;
+      wrapper = (type === 1)  // Element Node
+          ? (wrapper ? element : c)
+          : (type === 3)  // Text Node
+          ? (notWs.test(c.nodeValue) ? element : wrapper)
+          : wrapper;
+    }
+    return wrapper === element ? undefined : wrapper;
+  }
+
+  /** Given triples of [style, pattern, context] returns a lexing function,
+    * The lexing function interprets the patterns to find token boundaries and
+    * returns a decoration list of the form
+    * [index_0, style_0, index_1, style_1, ..., index_n, style_n]
+    * where index_n is an index into the sourceCode, and style_n is a style
+    * constant like PR_PLAIN.  index_n-1 <= index_n, and style_n-1 applies to
+    * all characters in sourceCode[index_n-1:index_n].
+    *
+    * The stylePatterns is a list whose elements have the form
+    * [style : string, pattern : RegExp, DEPRECATED, shortcut : string].
+    *
+    * Style is a style constant like PR_PLAIN, or can be a string of the
+    * form 'lang-FOO', where FOO is a language extension describing the
+    * language of the portion of the token in $1 after pattern executes.
+    * E.g., if style is 'lang-lisp', and group 1 contains the text
+    * '(hello (world))', then that portion of the token will be passed to the
+    * registered lisp handler for formatting.
+    * The text before and after group 1 will be restyled using this decorator
+    * so decorators should take care that this doesn't result in infinite
+    * recursion.  For example, the HTML lexer rule for SCRIPT elements looks
+    * something like ['lang-js', /<[s]cript>(.+?)<\/script>/].  This may match
+    * '<script>foo()<\/script>', which would cause the current decorator to
+    * be called with '<script>' which would not match the same rule since
+    * group 1 must not be empty, so it would be instead styled as PR_TAG by
+    * the generic tag rule.  The handler registered for the 'js' extension would
+    * then be called with 'foo()', and finally, the current decorator would
+    * be called with '<\/script>' which would not match the original rule and
+    * so the generic tag rule would identify it as a tag.
+    *
+    * Pattern must only match prefixes, and if it matches a prefix, then that
+    * match is considered a token with the same style.
+    *
+    * Context is applied to the last non-whitespace, non-comment token
+    * recognized.
+    *
+    * Shortcut is an optional string of characters, any of which, if the first
+    * character, gurantee that this pattern and only this pattern matches.
+    *
+    * @param {Array} shortcutStylePatterns patterns that always start with
+    *   a known character.  Must have a shortcut string.
+    * @param {Array} fallthroughStylePatterns patterns that will be tried in
+    *   order if the shortcut ones fail.  May have shortcuts.
+    *
+    * @return {function (Object)} a
+    *   function that takes source code and returns a list of decorations.
+    */
+  function createSimpleLexer(shortcutStylePatterns, fallthroughStylePatterns) {
+    var shortcuts = {};
+    var tokenizer;
+    (function () {
+      var allPatterns = shortcutStylePatterns.concat(fallthroughStylePatterns);
+      var allRegexs = [];
+      var regexKeys = {};
+      for (var i = 0, n = allPatterns.length; i < n; ++i) {
+        var patternParts = allPatterns[i];
+        var shortcutChars = patternParts[3];
+        if (shortcutChars) {
+          for (var c = shortcutChars.length; --c >= 0;) {
+            shortcuts[shortcutChars.charAt(c)] = patternParts;
+          }
+        }
+        var regex = patternParts[1];
+        var k = '' + regex;
+        if (!regexKeys.hasOwnProperty(k)) {
+          allRegexs.push(regex);
+          regexKeys[k] = null;
+        }
+      }
+      allRegexs.push(/[\0-\uffff]/);
+      tokenizer = combinePrefixPatterns(allRegexs);
+    })();
+
+    var nPatterns = fallthroughStylePatterns.length;
+
+    /**
+     * Lexes job.sourceCode and produces an output array job.decorations of
+     * style classes preceded by the position at which they start in
+     * job.sourceCode in order.
+     *
+     * @param {Object} job an object like <pre>{
+     *    sourceCode: {string} sourceText plain text,
+     *    basePos: {int} position of job.sourceCode in the larger chunk of
+     *        sourceCode.
+     * }</pre>
+     */
+    var decorate = function (job) {
+      var sourceCode = job.sourceCode, basePos = job.basePos;
+      /** Even entries are positions in source in ascending order.  Odd enties
+        * are style markers (e.g., PR_COMMENT) that run from that position until
+        * the end.
+        * @type {Array.<number|string>}
+        */
+      var decorations = [basePos, PR_PLAIN];
+      var pos = 0;  // index into sourceCode
+      var tokens = sourceCode.match(tokenizer) || [];
+      var styleCache = {};
+
+      for (var ti = 0, nTokens = tokens.length; ti < nTokens; ++ti) {
+        var token = tokens[ti];
+        var style = styleCache[token];
+        var match = void 0;
+
+        var isEmbedded;
+        if (typeof style === 'string') {
+          isEmbedded = false;
+        } else {
+          var patternParts = shortcuts[token.charAt(0)];
+          if (patternParts) {
+            match = token.match(patternParts[1]);
+            style = patternParts[0];
+          } else {
+            for (var i = 0; i < nPatterns; ++i) {
+              patternParts = fallthroughStylePatterns[i];
+              match = token.match(patternParts[1]);
+              if (match) {
+                style = patternParts[0];
+                break;
+              }
+            }
+
+            if (!match) {  // make sure that we make progress
+              style = PR_PLAIN;
+            }
+          }
+
+          isEmbedded = style.length >= 5 && 'lang-' === style.substring(0, 5);
+          if (isEmbedded && !(match && typeof match[1] === 'string')) {
+            isEmbedded = false;
+            style = PR_SOURCE;
+          }
+
+          if (!isEmbedded) { styleCache[token] = style; }
+        }
+
+        var tokenStart = pos;
+        pos += token.length;
+
+        if (!isEmbedded) {
+          decorations.push(basePos + tokenStart, style);
+        } else {  // Treat group 1 as an embedded block of source code.
+          var embeddedSource = match[1];
+          var embeddedSourceStart = token.indexOf(embeddedSource);
+          var embeddedSourceEnd = embeddedSourceStart + embeddedSource.length;
+          if (match[2]) {
+            // If embeddedSource can be blank, then it would match at the
+            // beginning which would cause us to infinitely recurse on the
+            // entire token, so we catch the right context in match[2].
+            embeddedSourceEnd = token.length - match[2].length;
+            embeddedSourceStart = embeddedSourceEnd - embeddedSource.length;
+          }
+          var lang = style.substring(5);
+          // Decorate the left of the embedded source
+          appendDecorations(
+              basePos + tokenStart,
+              token.substring(0, embeddedSourceStart),
+              decorate, decorations);
+          // Decorate the embedded source
+          appendDecorations(
+              basePos + tokenStart + embeddedSourceStart,
+              embeddedSource,
+              langHandlerForExtension(lang, embeddedSource),
+              decorations);
+          // Decorate the right of the embedded section
+          appendDecorations(
+              basePos + tokenStart + embeddedSourceEnd,
+              token.substring(embeddedSourceEnd),
+              decorate, decorations);
+        }
+      }
+      job.decorations = decorations;
+    };
+    return decorate;
+  }
+
+  /** returns a function that produces a list of decorations from source text.
+    *
+    * This code treats ", ', and ` as string delimiters, and \ as a string
+    * escape.  It does not recognize perl's qq() style strings.
+    * It has no special handling for double delimiter escapes as in basic, or
+    * the tripled delimiters used in python, but should work on those regardless
+    * although in those cases a single string literal may be broken up into
+    * multiple adjacent string literals.
+    *
+    * It recognizes C, C++, and shell style comments.
+    *
+    * @param {Object} options a set of optional parameters.
+    * @return {function (Object)} a function that examines the source code
+    *     in the input job and builds the decoration list.
+    */
+  function sourceDecorator(options) {
+    var shortcutStylePatterns = [], fallthroughStylePatterns = [];
+    if (options['tripleQuotedStrings']) {
+      // '''multi-line-string''', 'single-line-string', and double-quoted
+      shortcutStylePatterns.push(
+          [PR_STRING,  /^(?:\'\'\'(?:[^\'\\]|\\[\s\S]|\'{1,2}(?=[^\']))*(?:\'\'\'|$)|\"\"\"(?:[^\"\\]|\\[\s\S]|\"{1,2}(?=[^\"]))*(?:\"\"\"|$)|\'(?:[^\\\']|\\[\s\S])*(?:\'|$)|\"(?:[^\\\"]|\\[\s\S])*(?:\"|$))/,
+           null, '\'"']);
+    } else if (options['multiLineStrings']) {
+      // 'multi-line-string', "multi-line-string"
+      shortcutStylePatterns.push(
+          [PR_STRING,  /^(?:\'(?:[^\\\']|\\[\s\S])*(?:\'|$)|\"(?:[^\\\"]|\\[\s\S])*(?:\"|$)|\`(?:[^\\\`]|\\[\s\S])*(?:\`|$))/,
+           null, '\'"`']);
+    } else {
+      // 'single-line-string', "single-line-string"
+      shortcutStylePatterns.push(
+          [PR_STRING,
+           /^(?:\'(?:[^\\\'\r\n]|\\.)*(?:\'|$)|\"(?:[^\\\"\r\n]|\\.)*(?:\"|$))/,
+           null, '"\'']);
+    }
+    if (options['verbatimStrings']) {
+      // verbatim-string-literal production from the C# grammar.  See issue 93.
+      fallthroughStylePatterns.push(
+          [PR_STRING, /^@\"(?:[^\"]|\"\")*(?:\"|$)/, null]);
+    }
+    var hc = options['hashComments'];
+    if (hc) {
+      if (options['cStyleComments']) {
+        if (hc > 1) {  // multiline hash comments
+          shortcutStylePatterns.push(
+              [PR_COMMENT, /^#(?:##(?:[^#]|#(?!##))*(?:###|$)|.*)/, null, '#']);
+        } else {
+          // Stop C preprocessor declarations at an unclosed open comment
+          shortcutStylePatterns.push(
+              [PR_COMMENT, /^#(?:(?:define|e(?:l|nd)if|else|error|ifn?def|include|line|pragma|undef|warning)\b|[^\r\n]*)/,
+               null, '#']);
+        }
+        // #include <stdio.h>
+        fallthroughStylePatterns.push(
+            [PR_STRING,
+             /^<(?:(?:(?:\.\.\/)*|\/?)(?:[\w-]+(?:\/[\w-]+)+)?[\w-]+\.h(?:h|pp|\+\+)?|[a-z]\w*)>/,
+             null]);
+      } else {
+        shortcutStylePatterns.push([PR_COMMENT, /^#[^\r\n]*/, null, '#']);
+      }
+    }
+    if (options['cStyleComments']) {
+      fallthroughStylePatterns.push([PR_COMMENT, /^\/\/[^\r\n]*/, null]);
+      fallthroughStylePatterns.push(
+          [PR_COMMENT, /^\/\*[\s\S]*?(?:\*\/|$)/, null]);
+    }
+    var regexLiterals = options['regexLiterals'];
+    if (regexLiterals) {
+      /**
+       * @const
+       */
+      var regexExcls = regexLiterals > 1
+        ? ''  // Multiline regex literals
+        : '\n\r';
+      /**
+       * @const
+       */
+      var regexAny = regexExcls ? '.' : '[\\S\\s]';
+      /**
+       * @const
+       */
+      var REGEX_LITERAL = (
+          // A regular expression literal starts with a slash that is
+          // not followed by * or / so that it is not confused with
+          // comments.
+          '/(?=[^/*' + regexExcls + '])'
+          // and then contains any number of raw characters,
+          + '(?:[^/\\x5B\\x5C' + regexExcls + ']'
+          // escape sequences (\x5C),
+          +    '|\\x5C' + regexAny
+          // or non-nesting character sets (\x5B\x5D);
+          +    '|\\x5B(?:[^\\x5C\\x5D' + regexExcls + ']'
+          +             '|\\x5C' + regexAny + ')*(?:\\x5D|$))+'
+          // finally closed by a /.
+          + '/');
+      fallthroughStylePatterns.push(
+          ['lang-regex',
+           RegExp('^' + REGEXP_PRECEDER_PATTERN + '(' + REGEX_LITERAL + ')')
+           ]);
+    }
+
+    var types = options['types'];
+    if (types) {
+      fallthroughStylePatterns.push([PR_TYPE, types]);
+    }
+
+    var keywords = ("" + options['keywords']).replace(/^ | $/g, '');
+    if (keywords.length) {
+      fallthroughStylePatterns.push(
+          [PR_KEYWORD,
+           new RegExp('^(?:' + keywords.replace(/[\s,]+/g, '|') + ')\\b'),
+           null]);
+    }
+
+    shortcutStylePatterns.push([PR_PLAIN,       /^\s+/, null, ' \r\n\t\xA0']);
+
+    var punctuation =
+      // The Bash man page says
+
+      // A word is a sequence of characters considered as a single
+      // unit by GRUB. Words are separated by metacharacters,
+      // which are the following plus space, tab, and newline: { }
+      // | & $ ; < >
+      // ...
+      
+      // A word beginning with # causes that word and all remaining
+      // characters on that line to be ignored.
+
+      // which means that only a '#' after /(?:^|[{}|&$;<>\s])/ starts a
+      // comment but empirically
+      // $ echo {#}
+      // {#}
+      // $ echo \$#
+      // $#
+      // $ echo }#
+      // }#
+
+      // so /(?:^|[|&;<>\s])/ is more appropriate.
+
+      // http://gcc.gnu.org/onlinedocs/gcc-2.95.3/cpp_1.html#SEC3
+      // suggests that this definition is compatible with a
+      // default mode that tries to use a single token definition
+      // to recognize both bash/python style comments and C
+      // preprocessor directives.
+
+      // This definition of punctuation does not include # in the list of
+      // follow-on exclusions, so # will not be broken before if preceeded
+      // by a punctuation character.  We could try to exclude # after
+      // [|&;<>] but that doesn't seem to cause many major problems.
+      // If that does turn out to be a problem, we should change the below
+      // when hc is truthy to include # in the run of punctuation characters
+      // only when not followint [|&;<>].
+      '^.[^\\s\\w.$@\'"`/\\\\]*';
+    if (options['regexLiterals']) {
+      punctuation += '(?!\s*\/)';
+    }
+
+    fallthroughStylePatterns.push(
+        // TODO(mikesamuel): recognize non-latin letters and numerals in idents
+        [PR_LITERAL,     /^@[a-z_$][a-z_$@0-9]*/i, null],
+        [PR_TYPE,        /^(?:[@_]?[A-Z]+[a-z][A-Za-z_$@0-9]*|\w+_t\b)/, null],
+        [PR_PLAIN,       /^[a-z_$][a-z_$@0-9]*/i, null],
+        [PR_LITERAL,
+         new RegExp(
+             '^(?:'
+             // A hex number
+             + '0x[a-f0-9]+'
+             // or an octal or decimal number,
+             + '|(?:\\d(?:_\\d+)*\\d*(?:\\.\\d*)?|\\.\\d\\+)'
+             // possibly in scientific notation
+             + '(?:e[+\\-]?\\d+)?'
+             + ')'
+             // with an optional modifier like UL for unsigned long
+             + '[a-z]*', 'i'),
+         null, '0123456789'],
+        // Don't treat escaped quotes in bash as starting strings.
+        // See issue 144.
+        [PR_PLAIN,       /^\\[\s\S]?/, null],
+        [PR_PUNCTUATION, new RegExp(punctuation), null]);
+
+    return createSimpleLexer(shortcutStylePatterns, fallthroughStylePatterns);
+  }
+
+  var decorateSource = sourceDecorator({
+        'keywords': ALL_KEYWORDS,
+        'hashComments': true,
+        'cStyleComments': true,
+        'multiLineStrings': true,
+        'regexLiterals': true
+      });
+
+  /**
+   * Given a DOM subtree, wraps it in a list, and puts each line into its own
+   * list item.
+   *
+   * @param {Node} node modified in place.  Its content is pulled into an
+   *     HTMLOListElement, and each line is moved into a separate list item.
+   *     This requires cloning elements, so the input might not have unique
+   *     IDs after numbering.
+   * @param {boolean} isPreformatted true if white-space in text nodes should
+   *     be treated as significant.
+   */
+  function numberLines(node, opt_startLineNum, isPreformatted, opt_numberLines, opt_calloutModifiedLines) {
+    var nocode = /(?:^|\s)nocode(?:\s|$)/;
+    var lineBreak = /\r\n?|\n/;
+    var prefixes = [
+      { prefix: "*", className: "linemodified", },
+      { prefix: "-", className: "linedeleted", },
+      { prefix: "+", className: "lineadded", },
+    ];
+    opt_numberLines = (opt_numberLines == undefined) ? true : opt_numberLines;
+
+    var startsWith = function(str, prefix) {
+      return str.length >= prefix.length && str.substring(0, prefix.length) == prefix;
+    };
+  
+    var document = node.ownerDocument;
+  
+    var li = document.createElement('li');
+    while (node.firstChild) {
+      li.appendChild(node.firstChild);
+    }
+    // An array of lines.  We split below, so this is initialized to one
+    // un-split line.
+    var listItems = [li];
+  
+    function walk(node) {
+      var type = node.nodeType;
+      if (type == 1 && !nocode.test(node.className)) {  // Element
+        if ('br' === node.nodeName) {
+          breakAfter(node);
+          // Discard the <BR> since it is now flush against a </LI>.
+          if (node.parentNode) {
+            node.parentNode.removeChild(node);
+          }
+        } else {
+          for (var child = node.firstChild; child; child = child.nextSibling) {
+            walk(child);
+          }
+        }
+      } else if ((type == 3 || type == 4) && isPreformatted) {  // Text
+        var text = node.nodeValue;
+        var match = text.match(lineBreak);
+        if (match) {
+          var firstLine = text.substring(0, match.index);
+          node.nodeValue = firstLine;
+          var tail = text.substring(match.index + match[0].length);
+          if (tail) {
+            var parent = node.parentNode;
+            parent.insertBefore(
+              document.createTextNode(tail), node.nextSibling);
+          }
+          breakAfter(node);
+          if (!firstLine) {
+            // Don't leave blank text nodes in the DOM.
+            node.parentNode.removeChild(node);
+          }
+        }
+      }
+    }
+  
+    // Split a line after the given node.
+    function breakAfter(lineEndNode) {
+      // If there's nothing to the right, then we can skip ending the line
+      // here, and move root-wards since splitting just before an end-tag
+      // would require us to create a bunch of empty copies.
+      while (!lineEndNode.nextSibling) {
+        lineEndNode = lineEndNode.parentNode;
+        if (!lineEndNode) { return; }
+      }
+  
+      function breakLeftOf(limit, copy) {
+        // Clone shallowly if this node needs to be on both sides of the break.
+        var rightSide = copy ? limit.cloneNode(false) : limit;
+        var parent = limit.parentNode;
+        if (parent) {
+          // We clone the parent chain.
+          // This helps us resurrect important styling elements that cross lines.
+          // E.g. in <i>Foo<br>Bar</i>
+          // should be rewritten to <li><i>Foo</i></li><li><i>Bar</i></li>.
+          var parentClone = breakLeftOf(parent, 1);
+          // Move the clone and everything to the right of the original
+          // onto the cloned parent.
+          var next = limit.nextSibling;
+          parentClone.appendChild(rightSide);
+          for (var sibling = next; sibling; sibling = next) {
+            next = sibling.nextSibling;
+            parentClone.appendChild(sibling);
+          }
+        }
+        return rightSide;
+      }
+  
+      var copiedListItem = breakLeftOf(lineEndNode.nextSibling, 0);
+  
+      // Walk the parent chain until we reach an unattached LI.
+      for (var parent;
+           // Check nodeType since IE invents document fragments.
+           (parent = copiedListItem.parentNode) && parent.nodeType === 1;) {
+        copiedListItem = parent;
+      }
+      // Put it on the list of lines for later processing.
+      listItems.push(copiedListItem);
+    }
+  
+    // Split lines while there are lines left to split.
+    for (var i = 0;  // Number of lines that have been split so far.
+         i < listItems.length;  // length updated by breakAfter calls.
+         ++i) {
+      walk(listItems[i]);
+    }
+  
+    // Make sure numeric indices show correctly.
+    if (opt_startLineNum === (opt_startLineNum|0)) {
+      listItems[0].setAttribute('value', opt_startLineNum);
+    }
+  
+    var ol = document.createElement(opt_numberLines ? 'ol' : 'ul');
+    var classNames = [];
+    if (opt_numberLines) {
+      classNames.push('linenums');
+    }
+    if (opt_calloutModifiedLines) {
+      classNames.push('modifiedlines');
+    }
+    ol.className = classNames.join(" ");
+    var offset = Math.max(0, ((opt_startLineNum - 1 /* zero index */)) | 0) || 0;
+    for (var i = 0, n = listItems.length; i < n; ++i) {
+      li = listItems[i];
+      // Stick a class on the LIs so that stylesheets can
+      // color odd/even rows, or any other row pattern that
+      // is co-prime with 10.
+      var classNames = [];
+      if (opt_numberLines) {
+        classNames.push('L' + ((i + offset) % 10));
+      }
+      if (!li.firstChild) {
+        li.appendChild(document.createTextNode('\xA0'));
+      }
+      if (opt_calloutModifiedLines) {
+        // returns undefined to continue, true of found, false if not found
+        function findTextWithPrefix(node) {
+          if (!node) {
+            return;
+          }
+          var type = node.nodeType;
+          if (type == 1) { // Element
+            for (var child = node.firstChild; child; child = child.nextSibling) {
+              var result = findTextWithPrefix(child);
+              if (result !== undefined) {
+                  return result;
+              }
+            }
+          } else if (type == 3 || type == 4) {
+            var text = node.nodeValue;
+            if (text.length > 0) {
+              for (var pp = 0; pp < prefixes.length; ++pp) {
+                var prefixInfo = prefixes[pp];
+                if (startsWith(text, prefixInfo.prefix)) {
+                  node.nodeValue = text.substring(prefixInfo.prefix.length) || ' ';
+                  return prefixInfo.className;
+                }
+              }
+              return false;
+            }
+          }
+        }
+        var foundPrefix = findTextWithPrefix(li);
+        if (foundPrefix) {
+          classNames.push(foundPrefix);
+        }
+      }
+      li.className = classNames.join(" ");
+      ol.appendChild(li);
+    }
+  
+    node.appendChild(ol);
+  }
+  /**
+   * Breaks {@code job.sourceCode} around style boundaries in
+   * {@code job.decorations} and modifies {@code job.sourceNode} in place.
+   * @param {Object} job like <pre>{
+   *    sourceCode: {string} source as plain text,
+   *    sourceNode: {HTMLElement} the element containing the source,
+   *    spans: {Array.<number|Node>} alternating span start indices into source
+   *       and the text node or element (e.g. {@code <BR>}) corresponding to that
+   *       span.
+   *    decorations: {Array.<number|string} an array of style classes preceded
+   *       by the position at which they start in job.sourceCode in order
+   * }</pre>
+   * @private
+   */
+  function recombineTagsAndDecorations(job) {
+    var isIE8OrEarlier = /\bMSIE\s(\d+)/.exec(navigator.userAgent);
+    isIE8OrEarlier = isIE8OrEarlier && +isIE8OrEarlier[1] <= 8;
+    var newlineRe = /\n/g;
+  
+    var source = job.sourceCode;
+    var sourceLength = source.length;
+    // Index into source after the last code-unit recombined.
+    var sourceIndex = 0;
+  
+    var spans = job.spans;
+    var nSpans = spans.length;
+    // Index into spans after the last span which ends at or before sourceIndex.
+    var spanIndex = 0;
+  
+    var decorations = job.decorations;
+    var nDecorations = decorations.length;
+    // Index into decorations after the last decoration which ends at or before
+    // sourceIndex.
+    var decorationIndex = 0;
+  
+    // Remove all zero-length decorations.
+    decorations[nDecorations] = sourceLength;
+    var decPos, i;
+    for (i = decPos = 0; i < nDecorations;) {
+      if (decorations[i] !== decorations[i + 2]) {
+        decorations[decPos++] = decorations[i++];
+        decorations[decPos++] = decorations[i++];
+      } else {
+        i += 2;
+      }
+    }
+    nDecorations = decPos;
+  
+    // Simplify decorations.
+    for (i = decPos = 0; i < nDecorations;) {
+      var startPos = decorations[i];
+      // Conflate all adjacent decorations that use the same style.
+      var startDec = decorations[i + 1];
+      var end = i + 2;
+      while (end + 2 <= nDecorations && decorations[end + 1] === startDec) {
+        end += 2;
+      }
+      decorations[decPos++] = startPos;
+      decorations[decPos++] = startDec;
+      i = end;
+    }
+  
+    nDecorations = decorations.length = decPos;
+  
+    var sourceNode = job.sourceNode;
+    var oldDisplay;
+    if (sourceNode) {
+      oldDisplay = sourceNode.style.display;
+      sourceNode.style.display = 'none';
+    }
+    try {
+      var decoration = null;
+      while (spanIndex < nSpans) {
+        var spanStart = spans[spanIndex];
+        var spanEnd = spans[spanIndex + 2] || sourceLength;
+  
+        var decEnd = decorations[decorationIndex + 2] || sourceLength;
+  
+        var end = Math.min(spanEnd, decEnd);
+  
+        var textNode = spans[spanIndex + 1];
+        var styledText;
+        if (textNode.nodeType !== 1  // Don't muck with <BR>s or <LI>s
+            // Don't introduce spans around empty text nodes.
+            && (styledText = source.substring(sourceIndex, end))) {
+          // This may seem bizarre, and it is.  Emitting LF on IE causes the
+          // code to display with spaces instead of line breaks.
+          // Emitting Windows standard issue linebreaks (CRLF) causes a blank
+          // space to appear at the beginning of every line but the first.
+          // Emitting an old Mac OS 9 line separator makes everything spiffy.
+          if (isIE8OrEarlier) {
+            styledText = styledText.replace(newlineRe, '\r');
+          }
+          textNode.nodeValue = styledText;
+          var document = textNode.ownerDocument;
+          var span = document.createElement('span');
+          span.className = decorations[decorationIndex + 1];
+          var parentNode = textNode.parentNode;
+          parentNode.replaceChild(span, textNode);
+          span.appendChild(textNode);
+          if (sourceIndex < spanEnd) {  // Split off a text node.
+            spans[spanIndex + 1] = textNode
+                // TODO: Possibly optimize by using '' if there's no flicker.
+                = document.createTextNode(source.substring(end, spanEnd));
+            parentNode.insertBefore(textNode, span.nextSibling);
+          }
+        }
+  
+        sourceIndex = end;
+  
+        if (sourceIndex >= spanEnd) {
+          spanIndex += 2;
+        }
+        if (sourceIndex >= decEnd) {
+          decorationIndex += 2;
+        }
+      }
+    } finally {
+      if (sourceNode) {
+        sourceNode.style.display = oldDisplay;
+      }
+    }
+  }
+
+  /** Maps language-specific file extensions to handlers. */
+  var langHandlerRegistry = {};
+  /** Register a language handler for the given file extensions.
+    * @param {function (Object)} handler a function from source code to a list
+    *      of decorations.  Takes a single argument job which describes the
+    *      state of the computation.   The single parameter has the form
+    *      {@code {
+    *        sourceCode: {string} as plain text.
+    *        decorations: {Array.<number|string>} an array of style classes
+    *                     preceded by the position at which they start in
+    *                     job.sourceCode in order.
+    *                     The language handler should assigned this field.
+    *        basePos: {int} the position of source in the larger source chunk.
+    *                 All positions in the output decorations array are relative
+    *                 to the larger source chunk.
+    *      } }
+    * @param {Array.<string>} fileExtensions
+    */
+  function registerLangHandler(handler, fileExtensions) {
+    for (var i = fileExtensions.length; --i >= 0;) {
+      var ext = fileExtensions[i];
+      if (!langHandlerRegistry.hasOwnProperty(ext)) {
+        langHandlerRegistry[ext] = handler;
+      } else if (win['console']) {
+        console['warn']('cannot override language handler %s', ext);
+      }
+    }
+  }
+  function langHandlerForExtension(extension, source) {
+    if (!(extension && langHandlerRegistry.hasOwnProperty(extension))) {
+      // Treat it as markup if the first non whitespace character is a < and
+      // the last non-whitespace character is a >.
+      extension = /^\s*</.test(source)
+          ? 'default-markup'
+          : 'default-code';
+    }
+    return langHandlerRegistry[extension];
+  }
+  registerLangHandler(decorateSource, ['default-code']);
+  registerLangHandler(
+      createSimpleLexer(
+          [],
+          [
+           [PR_PLAIN,       /^[^<?]+/],
+           [PR_DECLARATION, /^<!\w[^>]*(?:>|$)/],
+           [PR_COMMENT,     /^<\!--[\s\S]*?(?:-\->|$)/],
+           // Unescaped content in an unknown language
+           ['lang-',        /^<\?([\s\S]+?)(?:\?>|$)/],
+           ['lang-',        /^<%([\s\S]+?)(?:%>|$)/],
+           [PR_PUNCTUATION, /^(?:<[%?]|[%?]>)/],
+           ['lang-',        /^<xmp\b[^>]*>([\s\S]+?)<\/xmp\b[^>]*>/i],
+           // Unescaped content in javascript.  (Or possibly vbscript).
+           ['lang-js',      /^<script\b[^>]*>([\s\S]*?)(<\/script\b[^>]*>)/i],
+           // Contains unescaped stylesheet content
+           ['lang-css',     /^<style\b[^>]*>([\s\S]*?)(<\/style\b[^>]*>)/i],
+           ['lang-in.tag',  /^(<\/?[a-z][^<>]*>)/i]
+          ]),
+      ['default-markup', 'htm', 'html', 'mxml', 'xhtml', 'xml', 'xsl']);
+  registerLangHandler(
+      createSimpleLexer(
+          [
+           [PR_PLAIN,        /^[\s]+/, null, ' \t\r\n'],
+           [PR_ATTRIB_VALUE, /^(?:\"[^\"]*\"?|\'[^\']*\'?)/, null, '\"\'']
+           ],
+          [
+           [PR_TAG,          /^^<\/?[a-z](?:[\w.:-]*\w)?|\/?>$/i],
+           [PR_ATTRIB_NAME,  /^(?!style[\s=]|on)[a-z](?:[\w:-]*\w)?/i],
+           ['lang-uq.val',   /^=\s*([^>\'\"\s]*(?:[^>\'\"\s\/]|\/(?=\s)))/],
+           [PR_PUNCTUATION,  /^[=<>\/]+/],
+           ['lang-js',       /^on\w+\s*=\s*\"([^\"]+)\"/i],
+           ['lang-js',       /^on\w+\s*=\s*\'([^\']+)\'/i],
+           ['lang-js',       /^on\w+\s*=\s*([^\"\'>\s]+)/i],
+           ['lang-css',      /^style\s*=\s*\"([^\"]+)\"/i],
+           ['lang-css',      /^style\s*=\s*\'([^\']+)\'/i],
+           ['lang-css',      /^style\s*=\s*([^\"\'>\s]+)/i]
+           ]),
+      ['in.tag']);
+  registerLangHandler(
+      createSimpleLexer([], [[PR_ATTRIB_VALUE, /^[\s\S]+/]]), ['uq.val']);
+  registerLangHandler(sourceDecorator({
+          'keywords': CPP_KEYWORDS,
+          'hashComments': true,
+          'cStyleComments': true,
+          'types': C_TYPES
+        }), ['c', 'cc', 'cpp', 'cxx', 'cyc', 'm']);
+  registerLangHandler(sourceDecorator({
+          'keywords': 'null,true,false'
+        }), ['json']);
+  registerLangHandler(sourceDecorator({
+          'keywords': CSHARP_KEYWORDS,
+          'hashComments': true,
+          'cStyleComments': true,
+          'verbatimStrings': true,
+          'types': C_TYPES
+        }), ['cs']);
+  registerLangHandler(sourceDecorator({
+          'keywords': JAVA_KEYWORDS,
+          'cStyleComments': true
+        }), ['java']);
+  registerLangHandler(sourceDecorator({
+          'keywords': SH_KEYWORDS,
+          'hashComments': true,
+          'multiLineStrings': true
+        }), ['bash', 'bsh', 'csh', 'sh']);
+  registerLangHandler(sourceDecorator({
+          'keywords': PYTHON_KEYWORDS,
+          'hashComments': true,
+          'multiLineStrings': true,
+          'tripleQuotedStrings': true
+        }), ['cv', 'py', 'python']);
+  registerLangHandler(sourceDecorator({
+          'keywords': PERL_KEYWORDS,
+          'hashComments': true,
+          'multiLineStrings': true,
+          'regexLiterals': 2  // multiline regex literals
+        }), ['perl', 'pl', 'pm']);
+  registerLangHandler(sourceDecorator({
+          'keywords': RUBY_KEYWORDS,
+          'hashComments': true,
+          'multiLineStrings': true,
+          'regexLiterals': true
+        }), ['rb', 'ruby']);
+  registerLangHandler(sourceDecorator({
+          'keywords': JSCRIPT_KEYWORDS,
+          'cStyleComments': true,
+          'regexLiterals': true
+        }), ['javascript', 'js']);
+  registerLangHandler(sourceDecorator({
+          'keywords': COFFEE_KEYWORDS,
+          'hashComments': 3,  // ### style block comments
+          'cStyleComments': true,
+          'multilineStrings': true,
+          'tripleQuotedStrings': true,
+          'regexLiterals': true
+        }), ['coffee']);
+  registerLangHandler(sourceDecorator({
+          'keywords': RUST_KEYWORDS,
+          'cStyleComments': true,
+          'multilineStrings': true
+        }), ['rc', 'rs', 'rust']);
+  registerLangHandler(
+      createSimpleLexer([], [[PR_STRING, /^[\s\S]+/]]), ['regex']);
+
+  function applyDecorator(job) {
+    var opt_langExtension = job.langExtension;
+
+    try {
+      // Extract tags, and convert the source code to plain text.
+      var sourceAndSpans = extractSourceSpans(job.sourceNode, job.pre);
+      /** Plain text. @type {string} */
+      var source = sourceAndSpans.sourceCode;
+      job.sourceCode = source;
+      job.spans = sourceAndSpans.spans;
+      job.basePos = 0;
+
+      // Apply the appropriate language handler
+      langHandlerForExtension(opt_langExtension, source)(job);
+
+      // Integrate the decorations and tags back into the source code,
+      // modifying the sourceNode in place.
+      recombineTagsAndDecorations(job);
+    } catch (e) {
+      if (win['console']) {
+        console['log'](e && e['stack'] || e);
+      }
+    }
+  }
+
+  /**
+   * Pretty print a chunk of code.
+   * @param sourceCodeHtml {string} The HTML to pretty print.
+   * @param opt_langExtension {string} The language name to use.
+   *     Typically, a filename extension like 'cpp' or 'java'.
+   * @param opt_numberLines {number|boolean} True to number lines,
+   *     or the 1-indexed number of the first line in sourceCodeHtml.
+   */
+  function $prettyPrintOne(sourceCodeHtml, opt_langExtension, opt_numberLines) {
+    var container = document.createElement('div');
+    // This could cause images to load and onload listeners to fire.
+    // E.g. <img onerror="alert(1337)" src="nosuchimage.png">.
+    // We assume that the inner HTML is from a trusted source.
+    // The pre-tag is required for IE8 which strips newlines from innerHTML
+    // when it is injected into a <pre> tag.
+    // http://stackoverflow.com/questions/451486/pre-tag-loses-line-breaks-when-setting-innerhtml-in-ie
+    // http://stackoverflow.com/questions/195363/inserting-a-newline-into-a-pre-tag-ie-javascript
+    container.innerHTML = '<pre>' + sourceCodeHtml + '</pre>';
+    container = container.firstChild;
+    if (opt_numberLines) {
+      numberLines(container, opt_numberLines, true);
+    }
+
+    var job = {
+      langExtension: opt_langExtension,
+      numberLines: opt_numberLines,
+      sourceNode: container,
+      pre: 1
+    };
+    applyDecorator(job);
+    return container.innerHTML;
+  }
+
+   /**
+    * Find all the {@code <pre>} and {@code <code>} tags in the DOM with
+    * {@code class=prettyprint} and prettify them.
+    *
+    * @param {Function} opt_whenDone called when prettifying is done.
+    * @param {HTMLElement|HTMLDocument} opt_root an element or document
+    *   containing all the elements to pretty print.
+    *   Defaults to {@code document.body}.
+    */
+  function $prettyPrint(opt_whenDone, opt_root) {
+    var root = opt_root || document.body;
+    var doc = root.ownerDocument || document;
+    function byTagName(tn) { return root.getElementsByTagName(tn); }
+    // fetch a list of nodes to rewrite
+    var codeSegments = [byTagName('pre'), byTagName('code'), byTagName('xmp')];
+    var elements = [];
+    for (var i = 0; i < codeSegments.length; ++i) {
+      for (var j = 0, n = codeSegments[i].length; j < n; ++j) {
+        elements.push(codeSegments[i][j]);
+      }
+    }
+    codeSegments = null;
+
+    var clock = Date;
+    if (!clock['now']) {
+      clock = { 'now': function () { return +(new Date); } };
+    }
+
+    // The loop is broken into a series of continuations to make sure that we
+    // don't make the browser unresponsive when rewriting a large page.
+    var k = 0;
+    var prettyPrintingJob;
+
+    var langExtensionRe = /\blang(?:uage)?-([\w.]+)(?!\S)/;
+    var prettyPrintRe = /\bprettyprint\b/;
+    var prettyPrintedRe = /\bprettyprinted\b/;
+    var preformattedTagNameRe = /pre|xmp/i;
+    var codeRe = /^code$/i;
+    var preCodeXmpRe = /^(?:pre|code|xmp)$/i;
+    var EMPTY = {};
+
+    function doWork() {
+      var endTime = (win['PR_SHOULD_USE_CONTINUATION'] ?
+                     clock['now']() + 250 /* ms */ :
+                     Infinity);
+      for (; k < elements.length && clock['now']() < endTime; k++) {
+        var cs = elements[k];
+
+        // Look for a preceding comment like
+        // <?prettify lang="..." linenums="..."?>
+        var attrs = EMPTY;
+        {
+          for (var preceder = cs; (preceder = preceder.previousSibling);) {
+            var nt = preceder.nodeType;
+            // <?foo?> is parsed by HTML 5 to a comment node (8)
+            // like <!--?foo?-->, but in XML is a processing instruction
+            var value = (nt === 7 || nt === 8) && preceder.nodeValue;
+            if (value
+                ? !/^\??prettify\b/.test(value)
+                : (nt !== 3 || /\S/.test(preceder.nodeValue))) {
+              // Skip over white-space text nodes but not others.
+              break;
+            }
+            if (value) {
+              attrs = {};
+              value.replace(
+                  /\b(\w+)=([\w:.%+-]+)/g,
+                function (_, name, value) { attrs[name] = value; });
+              break;
+            }
+          }
+        }
+
+        var className = cs.className;
+        if ((attrs !== EMPTY || prettyPrintRe.test(className))
+            // Don't redo this if we've already done it.
+            // This allows recalling pretty print to just prettyprint elements
+            // that have been added to the page since last call.
+            && !prettyPrintedRe.test(className)) {
+
+          // make sure this is not nested in an already prettified element
+          var nested = false;
+          for (var p = cs.parentNode; p; p = p.parentNode) {
+            var tn = p.tagName;
+            if (preCodeXmpRe.test(tn)
+                && p.className && prettyPrintRe.test(p.className)) {
+              nested = true;
+              break;
+            }
+          }
+          if (!nested) {
+            // Mark done.  If we fail to prettyprint for whatever reason,
+            // we shouldn't try again.
+            cs.className += ' prettyprinted';
+
+            // If the classes includes a language extensions, use it.
+            // Language extensions can be specified like
+            //     <pre class="prettyprint lang-cpp">
+            // the language extension "cpp" is used to find a language handler
+            // as passed to PR.registerLangHandler.
+            // HTML5 recommends that a language be specified using "language-"
+            // as the prefix instead.  Google Code Prettify supports both.
+            // http://dev.w3.org/html5/spec-author-view/the-code-element.html
+            var langExtension = attrs['lang'];
+            if (!langExtension) {
+              langExtension = className.match(langExtensionRe);
+              // Support <pre class="prettyprint"><code class="language-c">
+              var wrapper;
+              if (!langExtension && (wrapper = childContentWrapper(cs))
+                  && codeRe.test(wrapper.tagName)) {
+                langExtension = wrapper.className.match(langExtensionRe);
+              }
+
+              if (langExtension) { langExtension = langExtension[1]; }
+            }
+
+            var preformatted;
+            if (preformattedTagNameRe.test(cs.tagName)) {
+              preformatted = 1;
+            } else {
+              var currentStyle = cs['currentStyle'];
+              var defaultView = doc.defaultView;
+              var whitespace = (
+                  currentStyle
+                  ? currentStyle['whiteSpace']
+                  : (defaultView
+                     && defaultView.getComputedStyle)
+                  ? defaultView.getComputedStyle(cs, null)
+                  .getPropertyValue('white-space')
+                  : 0);
+              preformatted = whitespace
+                  && 'pre' === whitespace.substring(0, 3);
+            }
+
+            // Look for a class like linenums or linenums:<n> where <n> is the
+            // 1-indexed number of the first line.
+            var lineNums = attrs['linenums'];
+            if (!(lineNums = lineNums === 'true' || +lineNums)) {
+              lineNums = className.match(/\blinenums\b(?::(\d+))?/);
+              lineNums =
+                lineNums
+                ? lineNums[1] && lineNums[1].length
+                  ? +lineNums[1] : true
+                : false;
+            }
+            var showLineMods = attrs['showlinemods'];
+            if (showLineMods === undefined) {
+              showLineMods = className.match(/\bshowlinemods\b/);
+            }
+            if (lineNums || showLineMods) { numberLines(cs, lineNums, preformatted, lineNums, showLineMods); }
+
+            // do the pretty printing
+            prettyPrintingJob = {
+              langExtension: langExtension,
+              sourceNode: cs,
+              numberLines: lineNums,
+              pre: preformatted
+            };
+            applyDecorator(prettyPrintingJob);
+          }
+        }
+      }
+      if (k < elements.length) {
+        // finish up in a continuation
+        setTimeout(doWork, 250);
+      } else if ('function' === typeof opt_whenDone) {
+        opt_whenDone();
+      }
+    }
+
+    doWork();
+  }
+
+  /**
+   * Contains functions for creating and registering new language handlers.
+   * @type {Object}
+   */
+  var PR = win['PR'] = {
+        'createSimpleLexer': createSimpleLexer,
+        'registerLangHandler': registerLangHandler,
+        'sourceDecorator': sourceDecorator,
+        'PR_ATTRIB_NAME': PR_ATTRIB_NAME,
+        'PR_ATTRIB_VALUE': PR_ATTRIB_VALUE,
+        'PR_COMMENT': PR_COMMENT,
+        'PR_DECLARATION': PR_DECLARATION,
+        'PR_KEYWORD': PR_KEYWORD,
+        'PR_LITERAL': PR_LITERAL,
+        'PR_NOCODE': PR_NOCODE,
+        'PR_PLAIN': PR_PLAIN,
+        'PR_PUNCTUATION': PR_PUNCTUATION,
+        'PR_SOURCE': PR_SOURCE,
+        'PR_STRING': PR_STRING,
+        'PR_TAG': PR_TAG,
+        'PR_TYPE': PR_TYPE,
+        'prettyPrintOne':
+           IN_GLOBAL_SCOPE
+             ? (win['prettyPrintOne'] = $prettyPrintOne)
+             : (prettyPrintOne = $prettyPrintOne),
+        'prettyPrint': prettyPrint =
+           IN_GLOBAL_SCOPE
+             ? (win['prettyPrint'] = $prettyPrint)
+             : (prettyPrint = $prettyPrint)
+      };
+
+  // Make PR available via the Asynchronous Module Definition (AMD) API.
+  // Per https://github.com/amdjs/amdjs-api/wiki/AMD:
+  // The Asynchronous Module Definition (AMD) API specifies a
+  // mechanism for defining modules such that the module and its
+  // dependencies can be asynchronously loaded.
+  // ...
+  // To allow a clear indicator that a global define function (as
+  // needed for script src browser loading) conforms to the AMD API,
+  // any global define function SHOULD have a property called "amd"
+  // whose value is an object. This helps avoid conflict with any
+  // other existing JavaScript code that could have defined a define()
+  // function that does not conform to the AMD API.
+  if (typeof define === "function" && define['amd']) {
+    define("google-code-prettify", [], function () {
+      return PR; 
+    });
+  }
+})();

+ 18 - 0
threejs/lessons/resources/rss-icon.svg

@@ -0,0 +1,18 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<svg xmlns="http://www.w3.org/2000/svg"
+     id="RSSicon"
+     viewBox="0 0 8 8" width="256" height="256">
+
+  <title>RSS feed icon</title>
+
+  <style type="text/css">
+    .button {stroke: none; fill: orange;}
+    .symbol {stroke: none; fill: white;}
+  </style>
+
+  <rect   class="button" width="8" height="8" rx="1.5" />
+  <circle class="symbol" cx="2" cy="6" r="1" />
+  <path   class="symbol" d="m 1,4 a 3,3 0 0 1 3,3 h 1 a 4,4 0 0 0 -4,-4 z" />
+  <path   class="symbol" d="m 1,2 a 5,5 0 0 1 5,5 h 1 a 6,6 0 0 0 -6,-6 z" />
+
+</svg>

+ 69 - 0
threejs/lessons/resources/threejs-lessons.css

@@ -0,0 +1,69 @@
+/* Licensed under a BSD license. See ../license.html for license */
+
+html, body {
+  background-color: #aaa;
+  font-family: Sans-Serif;
+}
+
+canvas {
+  background-color: #fff;
+  border: 1px solid black;
+}
+
+#uiContainer {
+  position: absolute;
+  top: 10px;
+  right: 10px;
+  z-index: 3;
+}
+
+.gman-slider-label {
+  float: left;
+  gbackground-color: green;
+}
+.gman-slider-value {
+  float: right;
+  gbackground-color: blue;
+}
+.gman-slider-upper {
+  gbackground-color: yellow;
+  height: 1.5em;
+}
+.gman-slider-outer {
+  gbackground-color: purple;
+  height: 2.5em;
+}
+.gman-slider-slider {
+  font-size: x-small;
+  gbackground-color: red;
+}
+
+/* styles to apply if in an iframe */
+
+body.iframe {
+  width: 100%;
+  height: 100%;
+  margin: 0px;
+  padding: 0px;
+  overflow: hidden;
+}
+
+.iframe>.description {
+  display: none;
+}
+
+.iframe>canvas {
+  width: 100%;
+  height: 100%;
+}
+
+.iframe canvas {
+  border: none;
+}
+
+.iframe>#example {
+  width: 100%;
+  height: 100%;
+}
+
+

BIN
threejs/lessons/resources/threejsfundamentals-icon-256.png


BIN
threejs/lessons/resources/threejsfundamentals-icon.png


BIN
threejs/lessons/resources/threejsfundamentals.jpg


+ 12 - 0
threejs/lessons/threejs-fundamentals.md

@@ -0,0 +1,12 @@
+Title: Three.js Fundamentals
+Description: Your first Three.js lesson starting with the fundamentals
+
+Coming Soon
+
+    // code coming soon too
+
+And examples
+
+{{{example url="../threejs-fundamentals.html" }}}
+
+

+ 9 - 0
threejs/lessons/toc.html

@@ -0,0 +1,9 @@
+<ul>
+  <li>Fundamentals</li>
+  <ul>
+    <li><a href="/threejs/lessons/threejs-fundamentals.html">Three.js Fundamentals</a></li>
+  </ul>
+</ul>
+<ul>
+  <li><a href="https://github.com/greggman/threejsfundamentals">github</a></li>
+</ul>

+ 11 - 0
threejs/resources/editor-fullscreen-icon.svg

@@ -0,0 +1,11 @@
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
+<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
+<svg preserveAspectRatio="none" width="100%" height="100%" viewBox="0 0 640 480" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" xml:space="preserve" style="fill-rule:evenodd;clip-rule:evenodd;stroke-linejoin:round;stroke-miterlimit:1.41421;">
+    <path d="M640,0L640,480L0,480L0,0L640,0ZM576,64L64,64L64,416L576,416L576,64Z" style="fill:rgb(0,0,0);"/>
+    <g transform="matrix(1,0,0,1,11,10)">
+        <path d="M85,86L214,86L171.127,128.873L259.882,217.627L214.627,262.882L125.873,174.127L85,215L85,86Z" style="fill:rgb(0,0,0);"/>
+    </g>
+    <g transform="matrix(-1,5.66554e-16,-5.66554e-16,-1,629,470)">
+        <path d="M85,86L214,86L171.127,128.873L259.882,217.627L214.627,262.882L125.873,174.127L85,215L85,86Z" style="fill:rgb(0,0,0);"/>
+    </g>
+</svg>

+ 11 - 0
threejs/resources/editor-unfullscreen-icon.svg

@@ -0,0 +1,11 @@
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
+<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
+<svg preserveAspectRatio="none" width="100%" height="100%" viewBox="0 0 640 480" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" xml:space="preserve" style="fill-rule:evenodd;clip-rule:evenodd;stroke-linejoin:round;stroke-miterlimit:1.41421;">
+    <path d="M640,0L640,480L0,480L0,0L640,0ZM576,64L64,64L64,416L576,416L576,64Z" style="fill:rgb(0,0,0);"/>
+    <g transform="matrix(1,0,0,1,284.118,121.118)">
+        <path d="M85,86L214,86L171.127,128.873L259.882,217.627L214.627,262.882L125.873,174.127L85,215L85,86Z" style="fill:rgb(0,0,0);"/>
+    </g>
+    <g transform="matrix(-1,5.66554e-16,-5.66554e-16,-1,355.882,358.882)">
+        <path d="M85,86L214,86L171.127,128.873L259.882,217.627L214.627,262.882L125.873,174.127L85,215L85,86Z" style="fill:rgb(0,0,0);"/>
+    </g>
+</svg>

Some files were not shown because too many files changed in this diff